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>
13 #include <atlsimpcoll.h>
18 WINE_DEFAULT_DEBUG_CHANNEL(stobject
);
19 #define DISPLAY_NAME_LEN 40
21 //BOOL WINAPI UnregisterDeviceNotification(HDEVNOTIFY Handle);
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
;
32 * @name EnumHotpluggedDevices
34 * Enumerates the connected safely removable devices.
37 * List of device instances, representing the currently attached devices.
39 * @return The error code.
42 HRESULT
EnumHotpluggedDevices(CSimpleArray
<DEVINST
> &devList
)
44 devList
.RemoveAll(); // Clear current devList
45 HDEVINFO hdev
= SetupDiGetClassDevs(NULL
, NULL
, 0, DIGCF_ALLCLASSES
| DIGCF_PRESENT
);
46 if (INVALID_HANDLE_VALUE
== hdev
)
48 SP_DEVINFO_DATA did
= { 0 };
49 did
.cbSize
= sizeof(did
);
51 // Enumerate all the attached devices.
52 for (int idev
= 0; SetupDiEnumDeviceInfo(hdev
, idev
, &did
); idev
++)
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);
60 cr
= CM_Get_DevNode_Registry_Property(did
.DevInst
, CM_DRP_DEVICEDESC
, NULL
, dispName
, &ulLength
, 0);
63 cr
= CM_Get_DevNode_Registry_Property(did
.DevInst
, CM_DRP_CAPABILITIES
, NULL
, &dwCapabilities
, &dwSize
, 0);
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
)) &&
74 devList
.Add(did
.DevInst
);
77 SetupDiDestroyDeviceInfoList(hdev
);
79 if (NO_ERROR
!= GetLastError() && ERROR_NO_MORE_ITEMS
!= GetLastError())
90 * Pops the balloon notification of the given notification icon.
93 * Provides interface for acquiring CSysTray information as required.
95 * Title for the balloon notification.
97 * Main content for the balloon notification.
99 * Represents the particular notification icon.
101 * @return The error code.
104 HRESULT
NotifyBalloon(CSysTray
* pSysTray
, LPCWSTR szTitle
= NULL
, LPCWSTR szInfo
= NULL
, UINT uId
= ID_ICON_HOTPLUG
)
106 NOTIFYICONDATA nim
= { 0 };
107 nim
.cbSize
= sizeof(NOTIFYICONDATA
);
109 nim
.hWnd
= pSysTray
->GetHWnd();
111 nim
.uFlags
= NIF_INFO
;
113 nim
.dwInfoFlags
= NIIF_INFO
;
115 StringCchCopy(nim
.szInfoTitle
, _countof(nim
.szInfoTitle
), szTitle
);
116 StringCchCopy(nim
.szInfo
, _countof(nim
.szInfo
), szInfo
);
117 BOOL ret
= Shell_NotifyIcon(NIM_MODIFY
, &nim
);
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.
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.
130 return ret
? S_OK
: E_FAIL
;
133 HRESULT STDMETHODCALLTYPE
Hotplug_Init(_In_ CSysTray
* pSysTray
)
135 TRACE("Hotplug_Init\n");
136 g_hIconHotplug
= LoadIcon(g_hInstance
, MAKEINTRESOURCE(IDI_HOTPLUG_OK
));
138 EnumHotpluggedDevices(g_devList
);
140 return pSysTray
->NotifyIcon(NIM_ADD
, ID_ICON_HOTPLUG
, g_hIconHotplug
, g_strTooltip
, NIS_HIDDEN
);
143 HRESULT STDMETHODCALLTYPE
Hotplug_Update(_In_ CSysTray
* pSysTray
)
145 TRACE("Hotplug_Update\n");
147 if(g_devList
.GetSize() || g_IsRemoving
)
148 return pSysTray
->NotifyIcon(NIM_MODIFY
, ID_ICON_HOTPLUG
, g_hIconHotplug
, g_strTooltip
);
150 return pSysTray
->NotifyIcon(NIM_MODIFY
, ID_ICON_HOTPLUG
, g_hIconHotplug
, g_strTooltip
, NIS_HIDDEN
);
153 HRESULT STDMETHODCALLTYPE
Hotplug_Shutdown(_In_ CSysTray
* pSysTray
)
155 TRACE("Hotplug_Shutdown\n");
159 return pSysTray
->NotifyIcon(NIM_DELETE
, ID_ICON_HOTPLUG
, NULL
, NULL
);
162 static void _RunHotplug(CSysTray
* pSysTray
)
164 ShellExecuteW(pSysTray
->GetHWnd(), L
"open", L
"rundll32.exe shell32.dll,Control_RunDLL hotplug.dll", NULL
, NULL
, SW_SHOWNORMAL
);
167 static void _ShowContextMenu(CSysTray
* pSysTray
)
169 HMENU hPopup
= CreatePopupMenu();
170 ULONG ulLength
= DISPLAY_NAME_LEN
* sizeof(WCHAR
);
172 for (INT index
= 0; index
< g_devList
.GetSize(); index
++)
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");
179 swprintf(menuName
, L
"Eject %wS", dispName
);
180 AppendMenuW(hPopup
, MF_STRING
, index
+1, menuName
);
183 SetForegroundWindow(pSysTray
->GetHWnd());
184 DWORD flags
= TPM_RETURNCMD
| TPM_NONOTIFY
| TPM_RIGHTALIGN
| TPM_BOTTOMALIGN
;
188 DWORD id
= TrackPopupMenuEx(hPopup
, flags
,
190 pSysTray
->GetHWnd(), NULL
);
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");
199 cr
= CM_Request_Device_Eject_Ex(g_devList
[id
], 0, 0, 0, 0, 0);
200 if (cr
!= CR_SUCCESS
)
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
);
208 //MessageBox(0, L"Device ejected successfully!! You can safely remove the device now!", L"Safely Remove Hardware", MB_OKCANCEL | MB_ICONINFORMATION);
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.
219 static void _ShowContextMenuR(CSysTray
* pSysTray
)
221 CString
strMenu((LPWSTR
)IDS_HOTPLUG_REMOVE_2
);
222 HMENU hPopup
= CreatePopupMenu();
223 AppendMenuW(hPopup
, MF_STRING
, IDS_HOTPLUG_REMOVE_2
, strMenu
);
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
);
242 HRESULT STDMETHODCALLTYPE
Hotplug_Message(_In_ CSysTray
* pSysTray
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
, LRESULT
&lResult
)
245 TRACE("Hotplug_Message uMsg=%d, wParam=%x, lParam=%x\n", uMsg
, wParam
, lParam
);
250 TRACE("Hotplug_Message: WM_CREATE\n");
251 DEV_BROADCAST_DEVICEINTERFACE NotificationFilter;
253 ZeroMemory(&NotificationFilter, sizeof(NotificationFilter));
254 NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
255 NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
257 g_hDevNotify = RegisterDeviceNotification(pSysTray->GetHWnd(), &NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE | DEVICE_NOTIFY_ALL_INTERFACE_CLASSES);
258 if (g_hDevNotify != NULL)
266 TRACE("Hotplug_Message: WM_USER+220\n");
270 return Hotplug_Init(pSysTray
);
272 return Hotplug_Shutdown(pSysTray
);
277 TRACE("Hotplug_Message: WM_USER+221\n");
280 lResult
= (LRESULT
)g_IsRunning
;
285 case ID_ICON_HOTPLUG
:
286 Hotplug_Update(pSysTray
);
294 _ShowContextMenu(pSysTray
);
297 case WM_LBUTTONDBLCLK
:
298 _RunHotplug(pSysTray
);
305 _ShowContextMenuR(pSysTray
);
308 case WM_RBUTTONDBLCLK
:
316 case WM_DEVICECHANGE
:
319 case DBT_DEVNODES_CHANGED
:
320 hr
= EnumHotpluggedDevices(g_devList
);
326 case DBT_DEVICEARRIVAL
:
328 case DBT_DEVICEQUERYREMOVE
:
330 case DBT_DEVICEQUERYREMOVEFAILED
:
332 case DBT_DEVICEREMOVECOMPLETE
:
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
);
339 case DBT_DEVICEREMOVEPENDING
:
345 if (!UnregisterDeviceNotification(hDeviceNotify))
352 TRACE("Hotplug_Message received for unknown ID %d, ignoring.\n");