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 //BOOL WINAPI UnregisterDeviceNotification(HDEVNOTIFY Handle);
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
;
29 * @name EnumHotpluggedDevices
31 * Enumerates the connected safely removable devices.
34 * List of device instances, representing the currently attached devices.
36 * @return The error code.
39 HRESULT
EnumHotpluggedDevices(CSimpleArray
<DEVINST
> &devList
)
41 devList
.RemoveAll(); // Clear current devList
42 HDEVINFO hdev
= SetupDiGetClassDevs(NULL
, NULL
, 0, DIGCF_ALLCLASSES
| DIGCF_PRESENT
);
43 if (INVALID_HANDLE_VALUE
== hdev
)
45 SP_DEVINFO_DATA did
= { 0 };
46 did
.cbSize
= sizeof(did
);
48 // Enumerate all the attached devices.
49 for (int idev
= 0; SetupDiEnumDeviceInfo(hdev
, idev
, &did
); idev
++)
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);
57 cr
= CM_Get_DevNode_Registry_Property(did
.DevInst
, CM_DRP_DEVICEDESC
, NULL
, dispName
, &ulLength
, 0);
60 cr
= CM_Get_DevNode_Registry_Property(did
.DevInst
, CM_DRP_CAPABILITIES
, NULL
, &dwCapabilities
, &dwSize
, 0);
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
)) &&
71 devList
.Add(did
.DevInst
);
74 SetupDiDestroyDeviceInfoList(hdev
);
76 if (NO_ERROR
!= GetLastError() && ERROR_NO_MORE_ITEMS
!= GetLastError())
87 * Pops the balloon notification of the given notification icon.
90 * Provides interface for acquiring CSysTray information as required.
92 * Title for the balloon notification.
94 * Main content for the balloon notification.
96 * Represents the particular notification icon.
98 * @return The error code.
101 HRESULT
NotifyBalloon(CSysTray
* pSysTray
, LPCWSTR szTitle
= NULL
, LPCWSTR szInfo
= NULL
, UINT uId
= ID_ICON_HOTPLUG
)
103 NOTIFYICONDATA nim
= { 0 };
104 nim
.cbSize
= sizeof(NOTIFYICONDATA
);
106 nim
.hWnd
= pSysTray
->GetHWnd();
108 nim
.uFlags
= NIF_INFO
;
110 nim
.dwInfoFlags
= NIIF_INFO
;
112 StringCchCopy(nim
.szInfoTitle
, _countof(nim
.szInfoTitle
), szTitle
);
113 StringCchCopy(nim
.szInfo
, _countof(nim
.szInfo
), szInfo
);
114 BOOL ret
= Shell_NotifyIcon(NIM_MODIFY
, &nim
);
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.
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.
127 return ret
? S_OK
: E_FAIL
;
130 HRESULT STDMETHODCALLTYPE
Hotplug_Init(_In_ CSysTray
* pSysTray
)
132 TRACE("Hotplug_Init\n");
133 g_hIconHotplug
= LoadIcon(g_hInstance
, MAKEINTRESOURCE(IDI_HOTPLUG_OK
));
135 EnumHotpluggedDevices(g_devList
);
137 return pSysTray
->NotifyIcon(NIM_ADD
, ID_ICON_HOTPLUG
, g_hIconHotplug
, g_strTooltip
, NIS_HIDDEN
);
140 HRESULT STDMETHODCALLTYPE
Hotplug_Update(_In_ CSysTray
* pSysTray
)
142 TRACE("Hotplug_Update\n");
144 if(g_devList
.GetSize() || g_IsRemoving
)
145 return pSysTray
->NotifyIcon(NIM_MODIFY
, ID_ICON_HOTPLUG
, g_hIconHotplug
, g_strTooltip
);
147 return pSysTray
->NotifyIcon(NIM_MODIFY
, ID_ICON_HOTPLUG
, g_hIconHotplug
, g_strTooltip
, NIS_HIDDEN
);
150 HRESULT STDMETHODCALLTYPE
Hotplug_Shutdown(_In_ CSysTray
* pSysTray
)
152 TRACE("Hotplug_Shutdown\n");
156 return pSysTray
->NotifyIcon(NIM_DELETE
, ID_ICON_HOTPLUG
, NULL
, NULL
);
159 static void _RunHotplug(CSysTray
* pSysTray
)
161 ShellExecuteW(pSysTray
->GetHWnd(), L
"open", L
"rundll32.exe shell32.dll,Control_RunDLL hotplug.dll", NULL
, NULL
, SW_SHOWNORMAL
);
164 static void _ShowContextMenu(CSysTray
* pSysTray
)
166 HMENU hPopup
= CreatePopupMenu();
167 ULONG ulLength
= DISPLAY_NAME_LEN
* sizeof(WCHAR
);
169 for (INT index
= 0; index
< g_devList
.GetSize(); index
++)
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");
176 swprintf(menuName
, L
"Eject %wS", dispName
);
177 AppendMenuW(hPopup
, MF_STRING
, index
+1, menuName
);
180 SetForegroundWindow(pSysTray
->GetHWnd());
181 DWORD flags
= TPM_RETURNCMD
| TPM_NONOTIFY
| TPM_RIGHTALIGN
| TPM_BOTTOMALIGN
;
185 DWORD id
= TrackPopupMenuEx(hPopup
, flags
,
187 pSysTray
->GetHWnd(), NULL
);
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");
196 cr
= CM_Request_Device_Eject_Ex(g_devList
[id
], 0, 0, 0, 0, 0);
197 if (cr
!= CR_SUCCESS
)
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
);
205 //MessageBox(0, L"Device ejected successfully!! You can safely remove the device now!", L"Safely Remove Hardware", MB_OKCANCEL | MB_ICONINFORMATION);
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.
216 static void _ShowContextMenuR(CSysTray
* pSysTray
)
218 CString
strMenu((LPWSTR
)IDS_HOTPLUG_REMOVE_2
);
219 HMENU hPopup
= CreatePopupMenu();
220 AppendMenuW(hPopup
, MF_STRING
, IDS_HOTPLUG_REMOVE_2
, strMenu
);
222 SetForegroundWindow(pSysTray
->GetHWnd());
223 DWORD flags
= TPM_RETURNCMD
| TPM_NONOTIFY
| TPM_RIGHTALIGN
| TPM_BOTTOMALIGN
;
227 DWORD id
= TrackPopupMenuEx(hPopup
, flags
,
229 pSysTray
->GetHWnd(), NULL
);
231 if (id
== IDS_HOTPLUG_REMOVE_2
)
233 _RunHotplug(pSysTray
);
239 HRESULT STDMETHODCALLTYPE
Hotplug_Message(_In_ CSysTray
* pSysTray
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
, LRESULT
&lResult
)
242 TRACE("Hotplug_Message uMsg=%d, wParam=%x, lParam=%x\n", uMsg
, wParam
, lParam
);
247 TRACE("Hotplug_Message: WM_CREATE\n");
248 DEV_BROADCAST_DEVICEINTERFACE NotificationFilter;
250 ZeroMemory(&NotificationFilter, sizeof(NotificationFilter));
251 NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
252 NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
254 g_hDevNotify = RegisterDeviceNotification(pSysTray->GetHWnd(), &NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE | DEVICE_NOTIFY_ALL_INTERFACE_CLASSES);
255 if (g_hDevNotify != NULL)
263 TRACE("Hotplug_Message: WM_USER+220\n");
267 return Hotplug_Init(pSysTray
);
269 return Hotplug_Shutdown(pSysTray
);
274 TRACE("Hotplug_Message: WM_USER+221\n");
277 lResult
= (LRESULT
)g_IsRunning
;
282 case ID_ICON_HOTPLUG
:
283 Hotplug_Update(pSysTray
);
291 _ShowContextMenu(pSysTray
);
294 case WM_LBUTTONDBLCLK
:
295 _RunHotplug(pSysTray
);
302 _ShowContextMenuR(pSysTray
);
305 case WM_RBUTTONDBLCLK
:
313 case WM_DEVICECHANGE
:
316 case DBT_DEVNODES_CHANGED
:
317 hr
= EnumHotpluggedDevices(g_devList
);
323 case DBT_DEVICEARRIVAL
:
325 case DBT_DEVICEQUERYREMOVE
:
327 case DBT_DEVICEQUERYREMOVEFAILED
:
329 case DBT_DEVICEREMOVECOMPLETE
:
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
);
336 case DBT_DEVICEREMOVEPENDING
:
342 if (!UnregisterDeviceNotification(hDeviceNotify))
349 TRACE("Hotplug_Message received for unknown ID %d, ignoring.\n");