[STOBJECT] Implement the hotplug and the power icons in the system tray
[reactos.git] / dll / shellext / stobject / hotplug.cpp
index fbc22ea..4b17a1b 100644 (file)
  * PROJECT:     ReactOS system libraries
  * LICENSE:     GPL - See COPYING in the top level directory
  * FILE:        dll/shellext/stobject/hotplug.cpp
- * PURPOSE:     Hotplug notification icon handler
- * PROGRAMMERS: Eric Kohl <eric.kohl@reactos.org>
- *              David Quintana <gigaherz@gmail.com>
+ * PURPOSE:     Removable devices notification icon handler
+ * PROGRAMMERS: Shriraj Sawant a.k.a SR13 <sr.official@hotmail.com> 
  */
-
+#include <windows.h>
 #include "precomp.h"
+#include <mmsystem.h>
+#include <mmddk.h>
+#include <atlstr.h>
+#include <atlsimpcoll.h>
+#include <dbt.h>
+#include <setupapi.h>
+#include <cfgmgr32.h>
 
 WINE_DEFAULT_DEBUG_CHANNEL(stobject);
+#define DISPLAY_NAME_LEN 40
+
+//BOOL WINAPI UnregisterDeviceNotification(HDEVNOTIFY Handle);
 
+CSimpleArray<DEVINST> g_devList;
+/*static HDEVNOTIFY g_hDevNotify = NULL;*/
 static HICON g_hIconHotplug = NULL;
+static LPCWSTR g_strTooltip = L"Safely Remove Hardware and Eject Media";
+static WCHAR g_strMenuSel[DISPLAY_NAME_LEN];
 static BOOL g_IsRunning = FALSE;
-
-
-HRESULT STDMETHODCALLTYPE Hotplug_Init(_In_ CSysTray * pSysTray)
+static BOOL g_IsRemoving = FALSE;
+
+/*++
+* @name EnumHotpluggedDevices
+*
+* Enumerates the connected safely removable devices.
+*
+* @param devList
+*        List of device instances, representing the currently attached devices.
+*
+* @return The error code.
+*
+*--*/
+HRESULT EnumHotpluggedDevices(CSimpleArray<DEVINST> &devList)
 {
-    WCHAR strTooltip[128];
+    devList.RemoveAll(); // Clear current devList
+    HDEVINFO hdev = SetupDiGetClassDevs(NULL, NULL, 0, DIGCF_ALLCLASSES | DIGCF_PRESENT);
+    if (INVALID_HANDLE_VALUE == hdev)
+        return E_HANDLE;
+    SP_DEVINFO_DATA did = { 0 };
+    did.cbSize = sizeof(did);
+
+    // Enumerate all the attached devices.
+    for (int idev = 0; SetupDiEnumDeviceInfo(hdev, idev, &did); idev++)
+    {
+        DWORD dwCapabilities = 0, dwSize = sizeof(dwCapabilities);
+        WCHAR dispName[DISPLAY_NAME_LEN];
+        ULONG ulStatus = 0, ulPnum = 0, ulLength = DISPLAY_NAME_LEN * sizeof(WCHAR);
+        CONFIGRET cr = CM_Get_DevNode_Status(&ulStatus, &ulPnum, did.DevInst, 0);
+        if (cr != CR_SUCCESS)
+            continue;
+        cr = CM_Get_DevNode_Registry_Property(did.DevInst, CM_DRP_DEVICEDESC, NULL, dispName, &ulLength, 0);
+        if (cr != CR_SUCCESS)
+            continue;
+        cr = CM_Get_DevNode_Registry_Property(did.DevInst, CM_DRP_CAPABILITIES, NULL, &dwCapabilities, &dwSize, 0);
+        if (cr != CR_SUCCESS)
+            continue;
+
+        // Filter and make list of only the appropriate safely removable devices.
+        if ( (dwCapabilities & CM_DEVCAP_REMOVABLE) &&
+            !(dwCapabilities & CM_DEVCAP_DOCKDEVICE) &&
+            !(dwCapabilities & CM_DEVCAP_SURPRISEREMOVALOK) &&
+            ((dwCapabilities & CM_DEVCAP_EJECTSUPPORTED) || (ulStatus & DN_DISABLEABLE)) &&
+            !ulPnum)
+        {
+            devList.Add(did.DevInst);
+        }
+    }
+    SetupDiDestroyDeviceInfoList(hdev);
 
-    TRACE("Hotplug_Init\n");
+    if (NO_ERROR != GetLastError() && ERROR_NO_MORE_ITEMS != GetLastError())
+    {
+        return E_UNEXPECTED;
+    }
 
-    g_hIconHotplug = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_EXTRACT));
+    return S_OK;
+}
 
-    LoadStringW(g_hInstance, IDS_HOTPLUG_REMOVE_1, strTooltip, _countof(strTooltip));
+/*++
+* @name NotifyBalloon
+*
+* Pops the balloon notification of the given notification icon.
+*
+* @param pSysTray
+*        Provides interface for acquiring CSysTray information as required.
+* @param szTitle
+*        Title for the balloon notification.
+* @param szInfo
+*        Main content for the balloon notification.
+* @param uId
+*        Represents the particular notification icon.
+*
+* @return The error code.
+*
+*--*/
+HRESULT NotifyBalloon(CSysTray* pSysTray, LPCWSTR szTitle = NULL, LPCWSTR szInfo = NULL, UINT uId = ID_ICON_HOTPLUG)
+{
+    NOTIFYICONDATA nim = { 0 };
+    nim.cbSize = sizeof(NOTIFYICONDATA);
+    nim.uID = uId;
+    nim.hWnd = pSysTray->GetHWnd();
+
+    nim.uFlags = NIF_INFO;
+    nim.uTimeout = 10;
+    nim.dwInfoFlags = NIIF_INFO;
+
+    StringCchCopy(nim.szInfoTitle, _countof(nim.szInfoTitle), szTitle);
+    StringCchCopy(nim.szInfo, _countof(nim.szInfo), szInfo);
+    BOOL ret = Shell_NotifyIcon(NIM_MODIFY, &nim);
+
+    Sleep(10000); /* As per windows, the balloon notification remains visible for atleast 10 sec.
+                     This timer maintains the same condition.
+                     Also it is required so that the icon doesn't hide instantly after last device is removed,
+                     as that will prevent popping of notification.
+                  */
+    StringCchCopy(nim.szInfoTitle, _countof(nim.szInfoTitle), L"");
+    StringCchCopy(nim.szInfo, _countof(nim.szInfo), L"");
+    ret = Shell_NotifyIcon(NIM_MODIFY, &nim);
+    g_IsRemoving = FALSE; /* This flag is used to prevent instant icon hiding after last device is removed.
+                             The above timer maintains the required state for the same.
+                          */
+    return ret ? S_OK : E_FAIL;
+}
 
+HRESULT STDMETHODCALLTYPE Hotplug_Init(_In_ CSysTray * pSysTray)
+{ 
+    TRACE("Hotplug_Init\n");
+    g_hIconHotplug = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_HOTPLUG_OK));
     g_IsRunning = TRUE;
+    EnumHotpluggedDevices(g_devList);
 
-    return pSysTray->NotifyIcon(NIM_ADD, ID_ICON_HOTPLUG, g_hIconHotplug, strTooltip);
+    return pSysTray->NotifyIcon(NIM_ADD, ID_ICON_HOTPLUG, g_hIconHotplug, g_strTooltip, NIS_HIDDEN);
 }
 
 HRESULT STDMETHODCALLTYPE Hotplug_Update(_In_ CSysTray * pSysTray)
 {
     TRACE("Hotplug_Update\n");
-    return S_OK;
+
+    if(g_devList.GetSize() || g_IsRemoving)
+        return pSysTray->NotifyIcon(NIM_MODIFY, ID_ICON_HOTPLUG, g_hIconHotplug, g_strTooltip);
+    else
+        return pSysTray->NotifyIcon(NIM_MODIFY, ID_ICON_HOTPLUG, g_hIconHotplug, g_strTooltip, NIS_HIDDEN);
 }
 
 HRESULT STDMETHODCALLTYPE Hotplug_Shutdown(_In_ CSysTray * pSysTray)
@@ -45,79 +159,112 @@ HRESULT STDMETHODCALLTYPE Hotplug_Shutdown(_In_ CSysTray * pSysTray)
     return pSysTray->NotifyIcon(NIM_DELETE, ID_ICON_HOTPLUG, NULL, NULL);
 }
 
-static void RunHotplug()
+static void _RunHotplug(CSysTray * pSysTray)
 {
-    ShellExecuteW(NULL, NULL, L"rundll32.exe", L"shell32.dll,Control_RunDLL hotplug.dll", NULL, SW_SHOWNORMAL);
+    ShellExecuteW(pSysTray->GetHWnd(), L"open", L"rundll32.exe shell32.dll,Control_RunDLL hotplug.dll", NULL, NULL, SW_SHOWNORMAL);
 }
 
-static void ShowContextMenu(CSysTray *pSysTray)
+static void _ShowContextMenu(CSysTray * pSysTray)
 {
-    WCHAR szBuffer[128];
-    DWORD id, msgPos;
-    HMENU hPopup;
-
-    LoadStringW(g_hInstance, IDS_HOTPLUG_REMOVE_2, szBuffer, _countof(szBuffer));
+    HMENU hPopup = CreatePopupMenu();
+    ULONG ulLength = DISPLAY_NAME_LEN * sizeof(WCHAR);
 
-    hPopup = CreatePopupMenu();
-    AppendMenuW(hPopup, MF_STRING, 1, szBuffer);
+    for (INT index = 0; index < g_devList.GetSize(); index++)
+    {
+        WCHAR dispName[DISPLAY_NAME_LEN], menuName[DISPLAY_NAME_LEN + 10];
+        CONFIGRET cr = CM_Get_DevNode_Registry_Property(g_devList[index], CM_DRP_DEVICEDESC, NULL, dispName, &ulLength, 0);
+        if (cr != CR_SUCCESS)
+            StrCpyW(dispName, L"Unknown Device");
 
-    msgPos = GetMessagePos();
+        swprintf(menuName, L"Eject %wS", dispName);
+        AppendMenuW(hPopup, MF_STRING, index+1, menuName);
+    }
 
     SetForegroundWindow(pSysTray->GetHWnd());
-    id = TrackPopupMenuEx(hPopup,
-                          TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTALIGN | TPM_BOTTOMALIGN,
-                          GET_X_LPARAM(msgPos),
-                          GET_Y_LPARAM(msgPos),
-                          pSysTray->GetHWnd(),
-                          NULL);
+    DWORD flags = TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTALIGN | TPM_BOTTOMALIGN;
+    POINT pt;
+    GetCursorPos(&pt);
 
-    DestroyMenu(hPopup);
+    DWORD id = TrackPopupMenuEx(hPopup, flags,
+        pt.x, pt.y,
+        pSysTray->GetHWnd(), NULL);
+
+    if (id > 0)
+    {
+        id--; // since array indices starts from zero.
+        CONFIGRET cr = CM_Get_DevNode_Registry_Property(g_devList[id], CM_DRP_DEVICEDESC, NULL, g_strMenuSel, &ulLength, 0);
+        if (cr != CR_SUCCESS)
+            StrCpyW(g_strMenuSel, L"Unknown Device");
+
+        cr = CM_Request_Device_Eject_Ex(g_devList[id], 0, 0, 0, 0, 0);
+        if (cr != CR_SUCCESS)
+        {
+            WCHAR strInfo[128];
+            swprintf(strInfo, L"Problem Ejecting %wS", g_strMenuSel);
+            MessageBox(0, L"The device cannot be stopped right now! Try stopping it again later!", strInfo, MB_OKCANCEL | MB_ICONEXCLAMATION);
+        }
+        else
+        {
+            //MessageBox(0, L"Device ejected successfully!! You can safely remove the device now!", L"Safely Remove Hardware", MB_OKCANCEL | MB_ICONINFORMATION);
+            g_IsRemoving = TRUE;
+            g_devList.RemoveAt(id); /* thing is.. even after removing id at this point, the devnode_change occurs after some seconds of sucessful removal
+                                       and since pendrive is still plugged in it gets enumerated, if problem number is not filtered.
+                                    */
+        }
+    }
 
-    if (id == 1)
-        RunHotplug();
+    DestroyMenu(hPopup);
 }
 
-static
-VOID
-ShowHotplugPopupMenu(
-    HWND hWnd)
+static void _ShowContextMenuR(CSysTray * pSysTray)
 {
-#if 0
-    DWORD id, msgPos;
-
+    CString strMenu((LPWSTR)IDS_HOTPLUG_REMOVE_2);
     HMENU hPopup = CreatePopupMenu();
+    AppendMenuW(hPopup, MF_STRING, IDS_HOTPLUG_REMOVE_2, strMenu);
 
-    // FIXME
-    AppendMenuW(hPopup, MF_STRING, IDS_VOL_OPEN, strOpen);
-
-    msgPos = GetMessagePos();
-
-    SetForegroundWindow(hWnd);
-    id = TrackPopupMenuEx(hPopup,
-                          TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTALIGN | TPM_BOTTOMALIGN,
-                          GET_X_LPARAM(msgPos),
-                          GET_Y_LPARAM(msgPos),
-                          hWnd,
-                          NULL);
+    SetForegroundWindow(pSysTray->GetHWnd());
+    DWORD flags = TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTALIGN | TPM_BOTTOMALIGN;
+    POINT pt;
+    GetCursorPos(&pt);
 
-    DestroyMenu(hPopup);
+    DWORD id = TrackPopupMenuEx(hPopup, flags,
+        pt.x, pt.y,
+        pSysTray->GetHWnd(), NULL);
 
-    if (id != 0)
+    if (id == IDS_HOTPLUG_REMOVE_2)
     {
-        // FIXME
+        _RunHotplug(pSysTray);
     }
-#endif
+
+    DestroyMenu(hPopup);
 }
 
-HRESULT STDMETHODCALLTYPE Hotplug_Message(_In_ CSysTray *pSysTray, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT &lResult)
+HRESULT STDMETHODCALLTYPE Hotplug_Message(_In_ CSysTray * pSysTray, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT &lResult)
 {
+    HRESULT hr = E_FAIL;
     TRACE("Hotplug_Message uMsg=%d, wParam=%x, lParam=%x\n", uMsg, wParam, lParam);
 
     switch (uMsg)
     {
+        /*case WM_CREATE:
+            TRACE("Hotplug_Message: WM_CREATE\n");
+            DEV_BROADCAST_DEVICEINTERFACE NotificationFilter;
+
+            ZeroMemory(&NotificationFilter, sizeof(NotificationFilter));
+            NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
+            NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
+
+            g_hDevNotify = RegisterDeviceNotification(pSysTray->GetHWnd(), &NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE | DEVICE_NOTIFY_ALL_INTERFACE_CLASSES);
+            if (g_hDevNotify != NULL)
+            {
+                lResult = true;
+                return S_OK;
+            }
+            return S_FALSE;*/
+
         case WM_USER + 220:
             TRACE("Hotplug_Message: WM_USER+220\n");
-            if (wParam == 2)
+            if (wParam == 1)
             {
                 if (lParam == FALSE)
                     return Hotplug_Init(pSysTray);
@@ -128,7 +275,7 @@ HRESULT STDMETHODCALLTYPE Hotplug_Message(_In_ CSysTray *pSysTray, UINT uMsg, WP
 
         case WM_USER + 221:
             TRACE("Hotplug_Message: WM_USER+221\n");
-            if (wParam == 2)
+            if (wParam == 1)
             {
                 lResult = (LRESULT)g_IsRunning;
                 return S_OK;
@@ -141,22 +288,21 @@ HRESULT STDMETHODCALLTYPE Hotplug_Message(_In_ CSysTray *pSysTray, UINT uMsg, WP
             switch (lParam)
             {
                 case WM_LBUTTONDOWN:
-                    SetTimer(pSysTray->GetHWnd(), HOTPLUG_TIMER_ID, 500, NULL);
                     break;
 
                 case WM_LBUTTONUP:
+                    _ShowContextMenu(pSysTray);
                     break;
 
                 case WM_LBUTTONDBLCLK:
-                    KillTimer(pSysTray->GetHWnd(), HOTPLUG_TIMER_ID);
-                    RunHotplug();
+                    _RunHotplug(pSysTray);
                     break;
 
                 case WM_RBUTTONDOWN:
                     break;
 
                 case WM_RBUTTONUP:
-                    ShowContextMenu(pSysTray);
+                    _ShowContextMenuR(pSysTray);
                     break;
 
                 case WM_RBUTTONDBLCLK:
@@ -167,6 +313,41 @@ HRESULT STDMETHODCALLTYPE Hotplug_Message(_In_ CSysTray *pSysTray, UINT uMsg, WP
             }
             return S_OK;
 
+        case WM_DEVICECHANGE:
+            switch (wParam)
+            {
+                case DBT_DEVNODES_CHANGED:
+                    hr = EnumHotpluggedDevices(g_devList);
+                    if (FAILED(hr))
+                        return hr;
+
+                    lResult = true;
+                    break;
+                case DBT_DEVICEARRIVAL:
+                    break;
+                case DBT_DEVICEQUERYREMOVE:
+                    break;
+                case DBT_DEVICEQUERYREMOVEFAILED:
+                    break;
+                case DBT_DEVICEREMOVECOMPLETE:
+                    WCHAR strInfo[128];
+                    swprintf(strInfo, L"The %wS can now be safely removed from the system.", g_strMenuSel);
+                    NotifyBalloon(pSysTray, L"Safe to Remove Hardware", strInfo);
+
+                    lResult = true;
+                    break;
+                case DBT_DEVICEREMOVEPENDING:
+                    break;
+            }
+            return S_OK;
+
+        /*case WM_CLOSE:
+            if (!UnregisterDeviceNotification(hDeviceNotify))
+            {
+                return S_FALSE;
+            }
+            return S_OK;*/
+
         default:
             TRACE("Hotplug_Message received for unknown ID %d, ignoring.\n");
             return S_FALSE;
@@ -174,11 +355,3 @@ HRESULT STDMETHODCALLTYPE Hotplug_Message(_In_ CSysTray *pSysTray, UINT uMsg, WP
 
     return S_FALSE;
 }
-
-VOID
-Hotplug_OnTimer(HWND hWnd)
-{
-    TRACE("Hotplug_OnTimer\n!");
-    KillTimer(hWnd, HOTPLUG_TIMER_ID);
-    ShowHotplugPopupMenu(hWnd);
-}