[STOBJECT] Implement the hotplug and the power icons in the system tray
[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 #include <windows.h>
9 #include "precomp.h"
10 #include <mmsystem.h>
11 #include <mmddk.h>
12 #include <atlstr.h>
13 #include <atlsimpcoll.h>
14 #include <dbt.h>
15 #include <setupapi.h>
16 #include <cfgmgr32.h>
17
18 WINE_DEFAULT_DEBUG_CHANNEL(stobject);
19 #define DISPLAY_NAME_LEN 40
20
21 //BOOL WINAPI UnregisterDeviceNotification(HDEVNOTIFY Handle);
22
23 CSimpleArray<DEVINST> g_devList;
24 /*static HDEVNOTIFY g_hDevNotify = NULL;*/
25 static HICON g_hIconHotplug = NULL;
26 static LPCWSTR g_strTooltip = L"Safely Remove Hardware and Eject Media";
27 static WCHAR g_strMenuSel[DISPLAY_NAME_LEN];
28 static BOOL g_IsRunning = FALSE;
29 static BOOL g_IsRemoving = FALSE;
30
31 /*++
32 * @name EnumHotpluggedDevices
33 *
34 * Enumerates the connected safely removable devices.
35 *
36 * @param devList
37 * List of device instances, representing the currently attached devices.
38 *
39 * @return The error code.
40 *
41 *--*/
42 HRESULT EnumHotpluggedDevices(CSimpleArray<DEVINST> &devList)
43 {
44 devList.RemoveAll(); // Clear current devList
45 HDEVINFO hdev = SetupDiGetClassDevs(NULL, NULL, 0, DIGCF_ALLCLASSES | DIGCF_PRESENT);
46 if (INVALID_HANDLE_VALUE == hdev)
47 return E_HANDLE;
48 SP_DEVINFO_DATA did = { 0 };
49 did.cbSize = sizeof(did);
50
51 // Enumerate all the attached devices.
52 for (int idev = 0; SetupDiEnumDeviceInfo(hdev, idev, &did); idev++)
53 {
54 DWORD dwCapabilities = 0, dwSize = sizeof(dwCapabilities);
55 WCHAR dispName[DISPLAY_NAME_LEN];
56 ULONG ulStatus = 0, ulPnum = 0, ulLength = DISPLAY_NAME_LEN * sizeof(WCHAR);
57 CONFIGRET cr = CM_Get_DevNode_Status(&ulStatus, &ulPnum, did.DevInst, 0);
58 if (cr != CR_SUCCESS)
59 continue;
60 cr = CM_Get_DevNode_Registry_Property(did.DevInst, CM_DRP_DEVICEDESC, NULL, dispName, &ulLength, 0);
61 if (cr != CR_SUCCESS)
62 continue;
63 cr = CM_Get_DevNode_Registry_Property(did.DevInst, CM_DRP_CAPABILITIES, NULL, &dwCapabilities, &dwSize, 0);
64 if (cr != CR_SUCCESS)
65 continue;
66
67 // Filter and make list of only the appropriate safely removable devices.
68 if ( (dwCapabilities & CM_DEVCAP_REMOVABLE) &&
69 !(dwCapabilities & CM_DEVCAP_DOCKDEVICE) &&
70 !(dwCapabilities & CM_DEVCAP_SURPRISEREMOVALOK) &&
71 ((dwCapabilities & CM_DEVCAP_EJECTSUPPORTED) || (ulStatus & DN_DISABLEABLE)) &&
72 !ulPnum)
73 {
74 devList.Add(did.DevInst);
75 }
76 }
77 SetupDiDestroyDeviceInfoList(hdev);
78
79 if (NO_ERROR != GetLastError() && ERROR_NO_MORE_ITEMS != GetLastError())
80 {
81 return E_UNEXPECTED;
82 }
83
84 return S_OK;
85 }
86
87 /*++
88 * @name NotifyBalloon
89 *
90 * Pops the balloon notification of the given notification icon.
91 *
92 * @param pSysTray
93 * Provides interface for acquiring CSysTray information as required.
94 * @param szTitle
95 * Title for the balloon notification.
96 * @param szInfo
97 * Main content for the balloon notification.
98 * @param uId
99 * Represents the particular notification icon.
100 *
101 * @return The error code.
102 *
103 *--*/
104 HRESULT NotifyBalloon(CSysTray* pSysTray, LPCWSTR szTitle = NULL, LPCWSTR szInfo = NULL, UINT uId = ID_ICON_HOTPLUG)
105 {
106 NOTIFYICONDATA nim = { 0 };
107 nim.cbSize = sizeof(NOTIFYICONDATA);
108 nim.uID = uId;
109 nim.hWnd = pSysTray->GetHWnd();
110
111 nim.uFlags = NIF_INFO;
112 nim.uTimeout = 10;
113 nim.dwInfoFlags = NIIF_INFO;
114
115 StringCchCopy(nim.szInfoTitle, _countof(nim.szInfoTitle), szTitle);
116 StringCchCopy(nim.szInfo, _countof(nim.szInfo), szInfo);
117 BOOL ret = Shell_NotifyIcon(NIM_MODIFY, &nim);
118
119 Sleep(10000); /* As per windows, the balloon notification remains visible for atleast 10 sec.
120 This timer maintains the same condition.
121 Also it is required so that the icon doesn't hide instantly after last device is removed,
122 as that will prevent popping of notification.
123 */
124 StringCchCopy(nim.szInfoTitle, _countof(nim.szInfoTitle), L"");
125 StringCchCopy(nim.szInfo, _countof(nim.szInfo), L"");
126 ret = Shell_NotifyIcon(NIM_MODIFY, &nim);
127 g_IsRemoving = FALSE; /* This flag is used to prevent instant icon hiding after last device is removed.
128 The above timer maintains the required state for the same.
129 */
130 return ret ? S_OK : E_FAIL;
131 }
132
133 HRESULT STDMETHODCALLTYPE Hotplug_Init(_In_ CSysTray * pSysTray)
134 {
135 TRACE("Hotplug_Init\n");
136 g_hIconHotplug = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_HOTPLUG_OK));
137 g_IsRunning = TRUE;
138 EnumHotpluggedDevices(g_devList);
139
140 return pSysTray->NotifyIcon(NIM_ADD, ID_ICON_HOTPLUG, g_hIconHotplug, g_strTooltip, NIS_HIDDEN);
141 }
142
143 HRESULT STDMETHODCALLTYPE Hotplug_Update(_In_ CSysTray * pSysTray)
144 {
145 TRACE("Hotplug_Update\n");
146
147 if(g_devList.GetSize() || g_IsRemoving)
148 return pSysTray->NotifyIcon(NIM_MODIFY, ID_ICON_HOTPLUG, g_hIconHotplug, g_strTooltip);
149 else
150 return pSysTray->NotifyIcon(NIM_MODIFY, ID_ICON_HOTPLUG, g_hIconHotplug, g_strTooltip, NIS_HIDDEN);
151 }
152
153 HRESULT STDMETHODCALLTYPE Hotplug_Shutdown(_In_ CSysTray * pSysTray)
154 {
155 TRACE("Hotplug_Shutdown\n");
156
157 g_IsRunning = FALSE;
158
159 return pSysTray->NotifyIcon(NIM_DELETE, ID_ICON_HOTPLUG, NULL, NULL);
160 }
161
162 static void _RunHotplug(CSysTray * pSysTray)
163 {
164 ShellExecuteW(pSysTray->GetHWnd(), L"open", L"rundll32.exe shell32.dll,Control_RunDLL hotplug.dll", NULL, NULL, SW_SHOWNORMAL);
165 }
166
167 static void _ShowContextMenu(CSysTray * pSysTray)
168 {
169 HMENU hPopup = CreatePopupMenu();
170 ULONG ulLength = DISPLAY_NAME_LEN * sizeof(WCHAR);
171
172 for (INT index = 0; index < g_devList.GetSize(); index++)
173 {
174 WCHAR dispName[DISPLAY_NAME_LEN], menuName[DISPLAY_NAME_LEN + 10];
175 CONFIGRET cr = CM_Get_DevNode_Registry_Property(g_devList[index], CM_DRP_DEVICEDESC, NULL, dispName, &ulLength, 0);
176 if (cr != CR_SUCCESS)
177 StrCpyW(dispName, L"Unknown Device");
178
179 swprintf(menuName, L"Eject %wS", dispName);
180 AppendMenuW(hPopup, MF_STRING, index+1, menuName);
181 }
182
183 SetForegroundWindow(pSysTray->GetHWnd());
184 DWORD flags = TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTALIGN | TPM_BOTTOMALIGN;
185 POINT pt;
186 GetCursorPos(&pt);
187
188 DWORD id = TrackPopupMenuEx(hPopup, flags,
189 pt.x, pt.y,
190 pSysTray->GetHWnd(), NULL);
191
192 if (id > 0)
193 {
194 id--; // since array indices starts from zero.
195 CONFIGRET cr = CM_Get_DevNode_Registry_Property(g_devList[id], CM_DRP_DEVICEDESC, NULL, g_strMenuSel, &ulLength, 0);
196 if (cr != CR_SUCCESS)
197 StrCpyW(g_strMenuSel, L"Unknown Device");
198
199 cr = CM_Request_Device_Eject_Ex(g_devList[id], 0, 0, 0, 0, 0);
200 if (cr != CR_SUCCESS)
201 {
202 WCHAR strInfo[128];
203 swprintf(strInfo, L"Problem Ejecting %wS", g_strMenuSel);
204 MessageBox(0, L"The device cannot be stopped right now! Try stopping it again later!", strInfo, MB_OKCANCEL | MB_ICONEXCLAMATION);
205 }
206 else
207 {
208 //MessageBox(0, L"Device ejected successfully!! You can safely remove the device now!", L"Safely Remove Hardware", MB_OKCANCEL | MB_ICONINFORMATION);
209 g_IsRemoving = TRUE;
210 g_devList.RemoveAt(id); /* thing is.. even after removing id at this point, the devnode_change occurs after some seconds of sucessful removal
211 and since pendrive is still plugged in it gets enumerated, if problem number is not filtered.
212 */
213 }
214 }
215
216 DestroyMenu(hPopup);
217 }
218
219 static void _ShowContextMenuR(CSysTray * pSysTray)
220 {
221 CString strMenu((LPWSTR)IDS_HOTPLUG_REMOVE_2);
222 HMENU hPopup = CreatePopupMenu();
223 AppendMenuW(hPopup, MF_STRING, IDS_HOTPLUG_REMOVE_2, strMenu);
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 HRESULT STDMETHODCALLTYPE Hotplug_Message(_In_ CSysTray * pSysTray, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT &lResult)
243 {
244 HRESULT hr = E_FAIL;
245 TRACE("Hotplug_Message uMsg=%d, wParam=%x, lParam=%x\n", uMsg, wParam, lParam);
246
247 switch (uMsg)
248 {
249 /*case WM_CREATE:
250 TRACE("Hotplug_Message: WM_CREATE\n");
251 DEV_BROADCAST_DEVICEINTERFACE NotificationFilter;
252
253 ZeroMemory(&NotificationFilter, sizeof(NotificationFilter));
254 NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
255 NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
256
257 g_hDevNotify = RegisterDeviceNotification(pSysTray->GetHWnd(), &NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE | DEVICE_NOTIFY_ALL_INTERFACE_CLASSES);
258 if (g_hDevNotify != NULL)
259 {
260 lResult = true;
261 return S_OK;
262 }
263 return S_FALSE;*/
264
265 case WM_USER + 220:
266 TRACE("Hotplug_Message: WM_USER+220\n");
267 if (wParam == 1)
268 {
269 if (lParam == FALSE)
270 return Hotplug_Init(pSysTray);
271 else
272 return Hotplug_Shutdown(pSysTray);
273 }
274 return S_FALSE;
275
276 case WM_USER + 221:
277 TRACE("Hotplug_Message: WM_USER+221\n");
278 if (wParam == 1)
279 {
280 lResult = (LRESULT)g_IsRunning;
281 return S_OK;
282 }
283 return S_FALSE;
284
285 case ID_ICON_HOTPLUG:
286 Hotplug_Update(pSysTray);
287
288 switch (lParam)
289 {
290 case WM_LBUTTONDOWN:
291 break;
292
293 case WM_LBUTTONUP:
294 _ShowContextMenu(pSysTray);
295 break;
296
297 case WM_LBUTTONDBLCLK:
298 _RunHotplug(pSysTray);
299 break;
300
301 case WM_RBUTTONDOWN:
302 break;
303
304 case WM_RBUTTONUP:
305 _ShowContextMenuR(pSysTray);
306 break;
307
308 case WM_RBUTTONDBLCLK:
309 break;
310
311 case WM_MOUSEMOVE:
312 break;
313 }
314 return S_OK;
315
316 case WM_DEVICECHANGE:
317 switch (wParam)
318 {
319 case DBT_DEVNODES_CHANGED:
320 hr = EnumHotpluggedDevices(g_devList);
321 if (FAILED(hr))
322 return hr;
323
324 lResult = true;
325 break;
326 case DBT_DEVICEARRIVAL:
327 break;
328 case DBT_DEVICEQUERYREMOVE:
329 break;
330 case DBT_DEVICEQUERYREMOVEFAILED:
331 break;
332 case DBT_DEVICEREMOVECOMPLETE:
333 WCHAR strInfo[128];
334 swprintf(strInfo, L"The %wS can now be safely removed from the system.", g_strMenuSel);
335 NotifyBalloon(pSysTray, L"Safe to Remove Hardware", strInfo);
336
337 lResult = true;
338 break;
339 case DBT_DEVICEREMOVEPENDING:
340 break;
341 }
342 return S_OK;
343
344 /*case WM_CLOSE:
345 if (!UnregisterDeviceNotification(hDeviceNotify))
346 {
347 return S_FALSE;
348 }
349 return S_OK;*/
350
351 default:
352 TRACE("Hotplug_Message received for unknown ID %d, ignoring.\n");
353 return S_FALSE;
354 }
355
356 return S_FALSE;
357 }