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>
11 #include <atlsimpcoll.h>
16 #define DISPLAY_NAME_LEN 40
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
;
25 * @name EnumHotpluggedDevices
27 * Enumerates the connected safely removable devices.
30 * List of device instances, representing the currently attached devices.
32 * @return The error code.
35 HRESULT
EnumHotpluggedDevices(CSimpleArray
<DEVINST
> &devList
)
37 devList
.RemoveAll(); // Clear current devList
38 HDEVINFO hdev
= SetupDiGetClassDevs(NULL
, NULL
, 0, DIGCF_ALLCLASSES
| DIGCF_PRESENT
);
39 if (INVALID_HANDLE_VALUE
== hdev
)
41 SP_DEVINFO_DATA did
= { 0 };
42 did
.cbSize
= sizeof(did
);
44 // Enumerate all the attached devices.
45 for (int idev
= 0; SetupDiEnumDeviceInfo(hdev
, idev
, &did
); idev
++)
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);
53 cr
= CM_Get_DevNode_Registry_Property(did
.DevInst
, CM_DRP_CAPABILITIES
, NULL
, &dwCapabilities
, &dwSize
, 0);
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
)) &&
64 devList
.Add(did
.DevInst
);
67 SetupDiDestroyDeviceInfoList(hdev
);
69 if (NO_ERROR
!= GetLastError() && ERROR_NO_MORE_ITEMS
!= GetLastError())
80 * Pops the balloon notification of the given notification icon.
83 * Provides interface for acquiring CSysTray information as required.
85 * Title for the balloon notification.
87 * Main content for the balloon notification.
89 * Represents the particular notification icon.
91 * @return The error code.
94 HRESULT
NotifyBalloon(CSysTray
* pSysTray
, LPCWSTR szTitle
= NULL
, LPCWSTR szInfo
= NULL
, UINT uId
= ID_ICON_HOTPLUG
)
96 NOTIFYICONDATA nim
= { 0 };
98 nim
.cbSize
= sizeof(nim
);
100 nim
.hWnd
= pSysTray
->GetHWnd();
102 nim
.uFlags
= NIF_INFO
;
104 nim
.dwInfoFlags
= NIIF_INFO
;
106 StringCchCopy(nim
.szInfoTitle
, _countof(nim
.szInfoTitle
), szTitle
);
107 StringCchCopy(nim
.szInfo
, _countof(nim
.szInfo
), szInfo
);
108 BOOL ret
= Shell_NotifyIcon(NIM_MODIFY
, &nim
);
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.
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.
121 return ret
? S_OK
: E_FAIL
;
124 HRESULT STDMETHODCALLTYPE
Hotplug_Init(_In_ CSysTray
* pSysTray
)
126 TRACE("Hotplug_Init\n");
128 g_hIconHotplug
= LoadIcon(g_hInstance
, MAKEINTRESOURCE(IDI_HOTPLUG_OK
));
129 g_strHotplugTooltip
.LoadStringW(IDS_HOTPLUG_REMOVE_1
);
131 EnumHotpluggedDevices(g_devList
);
133 if (g_devList
.GetSize() > 0)
134 return pSysTray
->NotifyIcon(NIM_ADD
, ID_ICON_HOTPLUG
, g_hIconHotplug
, g_strHotplugTooltip
);
136 return pSysTray
->NotifyIcon(NIM_ADD
, ID_ICON_HOTPLUG
, g_hIconHotplug
, g_strHotplugTooltip
, NIS_HIDDEN
);
139 HRESULT STDMETHODCALLTYPE
Hotplug_Update(_In_ CSysTray
* pSysTray
)
141 TRACE("Hotplug_Update\n");
145 HRESULT STDMETHODCALLTYPE
Hotplug_Shutdown(_In_ CSysTray
* pSysTray
)
147 TRACE("Hotplug_Shutdown\n");
149 DestroyIcon(g_hIconHotplug
);
150 g_hIconHotplug
= NULL
;
152 return pSysTray
->NotifyIcon(NIM_DELETE
, ID_ICON_HOTPLUG
, NULL
, NULL
);
155 static void _RunHotplug(CSysTray
* pSysTray
)
157 ShellExecuteW(pSysTray
->GetHWnd(),
160 L
"shell32.dll,Control_RunDLL hotplug.dll",
165 static void _ShowContextMenu(CSysTray
* pSysTray
)
167 HMENU hPopup
= CreatePopupMenu();
168 ULONG ulLength
= DISPLAY_NAME_LEN
* sizeof(WCHAR
);
170 for (INT index
= 0; index
< g_devList
.GetSize(); index
++)
172 WCHAR dispName
[DISPLAY_NAME_LEN
];
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");
178 menuName
.Format(IDS_HOTPLUG_REMOVE_3
, dispName
);
179 AppendMenuW(hPopup
, MF_STRING
, index
+1, menuName
);
182 SetForegroundWindow(pSysTray
->GetHWnd());
183 DWORD flags
= TPM_RETURNCMD
| TPM_NONOTIFY
| TPM_RIGHTALIGN
| TPM_BOTTOMALIGN
;
187 DWORD id
= TrackPopupMenuEx(hPopup
, flags
,
189 pSysTray
->GetHWnd(), NULL
);
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");
198 cr
= CM_Request_Device_Eject_Ex(g_devList
[id
], 0, 0, 0, 0, 0);
199 if (cr
!= CR_SUCCESS
)
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
);
207 //MessageBox(0, L"Device ejected successfully!! You can safely remove the device now!", L"Safely Remove Hardware", MB_OK | MB_ICONINFORMATION);
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.
218 static void _ShowContextMenuR(CSysTray
* pSysTray
)
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
);
225 SetForegroundWindow(pSysTray
->GetHWnd());
226 DWORD flags
= TPM_RETURNCMD
| TPM_NONOTIFY
| TPM_RIGHTALIGN
| TPM_BOTTOMALIGN
;
230 DWORD id
= TrackPopupMenuEx(hPopup
, flags
,
232 pSysTray
->GetHWnd(), NULL
);
234 if (id
== IDS_HOTPLUG_REMOVE_2
)
236 _RunHotplug(pSysTray
);
245 _In_ CSysTray
*pSysTray
)
247 TRACE("HotplugDeviceTimer()\n");
249 EnumHotpluggedDevices(g_devList
);
251 if (g_devList
.GetSize() > 0)
252 pSysTray
->NotifyIcon(NIM_MODIFY
, ID_ICON_HOTPLUG
, g_hIconHotplug
, g_strHotplugTooltip
);
254 pSysTray
->NotifyIcon(NIM_MODIFY
, ID_ICON_HOTPLUG
, g_hIconHotplug
, g_strHotplugTooltip
, NIS_HIDDEN
);
258 HRESULT STDMETHODCALLTYPE
Hotplug_Message(_In_ CSysTray
* pSysTray
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
, LRESULT
&lResult
)
260 TRACE("Hotplug_Message uMsg=%d, wParam=%x, lParam=%x\n", uMsg
, wParam
, lParam
);
265 TRACE("Hotplug_Message: WM_USER+220\n");
266 if (wParam
== HOTPLUG_SERVICE_FLAG
)
270 pSysTray
->EnableService(HOTPLUG_SERVICE_FLAG
, TRUE
);
271 return Hotplug_Init(pSysTray
);
275 pSysTray
->EnableService(HOTPLUG_SERVICE_FLAG
, FALSE
);
276 return Hotplug_Shutdown(pSysTray
);
282 TRACE("Hotplug_Message: WM_USER+221\n");
283 if (wParam
== HOTPLUG_SERVICE_FLAG
)
285 lResult
= (LRESULT
)pSysTray
->IsServiceEnabled(HOTPLUG_SERVICE_FLAG
);
291 if (wParam
== HOTPLUG_TIMER_ID
)
293 KillTimer(pSysTray
->GetHWnd(), HOTPLUG_TIMER_ID
);
294 _ShowContextMenu(pSysTray
);
296 else if (wParam
== HOTPLUG_DEVICE_TIMER_ID
)
298 KillTimer(pSysTray
->GetHWnd(), HOTPLUG_DEVICE_TIMER_ID
);
299 HotplugDeviceTimer(pSysTray
);
303 case ID_ICON_HOTPLUG
:
307 SetTimer(pSysTray
->GetHWnd(), HOTPLUG_TIMER_ID
, GetDoubleClickTime(), NULL
);
313 case WM_LBUTTONDBLCLK
:
314 KillTimer(pSysTray
->GetHWnd(), HOTPLUG_TIMER_ID
);
315 _RunHotplug(pSysTray
);
322 _ShowContextMenuR(pSysTray
);
325 case WM_RBUTTONDBLCLK
:
333 case WM_DEVICECHANGE
:
336 case DBT_DEVNODES_CHANGED
:
337 TRACE("WM_DEVICECHANGE : DBT_DEVNODES_CHANGED\n");
338 SetTimer(pSysTray
->GetHWnd(), HOTPLUG_DEVICE_TIMER_ID
, 100, NULL
);
342 case DBT_DEVICEARRIVAL
:
344 case DBT_DEVICEQUERYREMOVE
:
346 case DBT_DEVICEQUERYREMOVEFAILED
:
348 case DBT_DEVICEREMOVECOMPLETE
:
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
);
355 case DBT_DEVICEREMOVEPENDING
:
361 TRACE("Hotplug_Message received for unknown ID %d, ignoring.\n");