OVR_Win32_DeviceStatus.cpp
Go to the documentation of this file.
00001 /************************************************************************************
00002 
00003 Filename    :   OVR_Win32_DeviceStatus.cpp
00004 Content     :   Win32 implementation of DeviceStatus.
00005 Created     :   January 24, 2013
00006 Authors     :   Lee Cooper
00007 
00008 Copyright   :   Copyright 2013 Oculus VR, Inc. All Rights reserved.
00009 
00010 Use of this software is subject to the terms of the Oculus license
00011 agreement provided at the time of installation or download, or which
00012 otherwise accompanies this software in either electronic or hard copy form.
00013 
00014 *************************************************************************************/
00015 
00016 #include "OVR_Win32_DeviceStatus.h"
00017 
00018 #include "OVR_Win32_HIDDevice.h"
00019 
00020 #include "Kernel/OVR_Log.h"
00021 
00022 #include <dbt.h>
00023 
00024 namespace OVR { namespace Win32 {
00025 
00026 static TCHAR windowClassName[] = TEXT("LibOVR_DeviceStatus_WindowClass");
00027 
00028 //-------------------------------------------------------------------------------------
00029 DeviceStatus::DeviceStatus(Notifier* const pClient)
00030         : pNotificationClient(pClient), LastTimerId(0)
00031 {       
00032 }
00033 
00034 bool DeviceStatus::Initialize()
00035 {
00036 
00037         WNDCLASS wndClass;
00038         wndClass.style         = CS_HREDRAW | CS_VREDRAW;
00039         wndClass.lpfnWndProc   = WindowsMessageCallback;
00040         wndClass.cbClsExtra    = 0;
00041         wndClass.cbWndExtra    = 0;
00042         wndClass.hInstance     = 0;
00043         wndClass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
00044         wndClass.hCursor       = LoadCursor(NULL, IDC_ARROW);
00045         wndClass.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
00046         wndClass.lpszMenuName  = NULL;
00047         wndClass.lpszClassName = windowClassName;
00048 
00049         if (!RegisterClass(&wndClass))
00050         {
00051                 OVR_ASSERT_LOG(false, ("Failed to register window class."));
00052                 return false;
00053         }
00054 
00055     // We're going to create a 'message-only' window. This will be hidden, can't be enumerated etc.
00056     // To do this we supply 'HWND_MESSAGE' as the hWndParent.
00057     // http://msdn.microsoft.com/en-us/library/ms632599%28VS.85%29.aspx#message_only
00058         hMessageWindow = CreateWindow(  windowClassName,
00059                                                                         windowClassName,
00060                                                                         WS_OVERLAPPEDWINDOW,
00061                                                                         CW_USEDEFAULT,
00062                                                                         CW_USEDEFAULT,
00063                                                                         CW_USEDEFAULT,
00064                                                                         CW_USEDEFAULT,
00065                                                                         HWND_MESSAGE,
00066                                                                         NULL,
00067                                                                         0,
00068                                                                         this);  // Pass this object via the CREATESTRUCT mechanism 
00069                                             // so that we can attach it to the window user data.
00070 
00071     if (hMessageWindow == NULL)
00072         {
00073                 OVR_ASSERT_LOG(false, ("Failed to create window."));
00074                 return false;
00075         }
00076 
00077     // According to MS, topmost windows receive WM_DEVICECHANGE faster.
00078         ::SetWindowPos(hMessageWindow, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
00079         UpdateWindow(hMessageWindow);
00080 
00081 
00082         // Register notification for additional HID messages.
00083     HIDDeviceManager* hidDeviceManager = new HIDDeviceManager(NULL);
00084         HidGuid = hidDeviceManager->GetHIDGuid();
00085     hidDeviceManager->Release();
00086 
00087         DEV_BROADCAST_DEVICEINTERFACE notificationFilter;
00088 
00089         ZeroMemory(&notificationFilter, sizeof(notificationFilter));
00090         notificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
00091         notificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
00092         //notificationFilter.dbcc_classguid = hidguid;
00093 
00094     // We need DEVICE_NOTIFY_ALL_INTERFACE_CLASSES to detect
00095     // HDMI plug/unplug events.
00096         hDeviceNotify = RegisterDeviceNotification(     
00097         hMessageWindow,
00098                 &notificationFilter,
00099                 DEVICE_NOTIFY_ALL_INTERFACE_CLASSES|DEVICE_NOTIFY_WINDOW_HANDLE);
00100 
00101         if (hDeviceNotify == NULL)
00102         {
00103                 OVR_ASSERT_LOG(false, ("Failed to register for device notifications."));
00104                 return false;
00105         }
00106 
00107         return true;
00108 }
00109 
00110 void DeviceStatus::ShutDown()
00111 {
00112         OVR_ASSERT(hMessageWindow);
00113 
00114         if (!UnregisterDeviceNotification(hDeviceNotify))
00115         {
00116                 OVR_ASSERT_LOG(false, ("Failed to unregister device notification."));
00117         }
00118 
00119         PostMessage(hMessageWindow, WM_CLOSE, 0, 0);
00120 
00121         while (hMessageWindow != NULL)
00122         {
00123                 ProcessMessages();
00124                 Sleep(1);
00125         }
00126 
00127     if (!UnregisterClass(windowClassName, NULL))
00128     {
00129         OVR_ASSERT_LOG(false, ("Failed to unregister window class."));
00130     }
00131 }
00132 
00133 DeviceStatus::~DeviceStatus()
00134 {    
00135         OVR_ASSERT_LOG(hMessageWindow == NULL, ("Need to call 'ShutDown' from DeviceManagerThread."));
00136 }
00137 
00138 void DeviceStatus::ProcessMessages()
00139 {
00140         OVR_ASSERT_LOG(hMessageWindow != NULL, ("Need to call 'Initialize' before first use."));
00141 
00142         MSG msg;
00143 
00144         // Note WM_DEVICECHANGED messages are dispatched but not retrieved by PeekMessage.
00145     // I think this is because they are pending, non-queued messages.
00146         while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) 
00147         {
00148                 TranslateMessage(&msg);
00149                 DispatchMessage(&msg);
00150         }
00151 }
00152 
00153 bool DeviceStatus::MessageCallback(WORD messageType, const String& devicePath)
00154 {
00155         bool rv = true;
00156         if (messageType == DBT_DEVICEARRIVAL)
00157         {
00158                 rv = pNotificationClient->OnMessage(Notifier::DeviceAdded, devicePath);
00159         }
00160         else if (messageType == DBT_DEVICEREMOVECOMPLETE)
00161         {
00162                 pNotificationClient->OnMessage(Notifier::DeviceRemoved, devicePath);
00163         }
00164         else
00165         {
00166                 OVR_ASSERT(0);
00167         }
00168         return rv;
00169 }
00170 
00171 void DeviceStatus::CleanupRecoveryTimer(UPInt index)
00172 {
00173     ::KillTimer(hMessageWindow, RecoveryTimers[index].TimerId);
00174     RecoveryTimers.RemoveAt(index);
00175 }
00176     
00177 DeviceStatus::RecoveryTimerDesc* 
00178 DeviceStatus::FindRecoveryTimer(UINT_PTR timerId, UPInt* pindex)
00179 {
00180     for (UPInt i = 0, n = RecoveryTimers.GetSize(); i < n; ++i)
00181     {
00182         RecoveryTimerDesc* pdesc = &RecoveryTimers[i];
00183         if (pdesc->TimerId == timerId)
00184         {
00185             *pindex = i;
00186             return pdesc;
00187         }
00188     }
00189     return NULL;
00190 }
00191 
00192 void DeviceStatus::FindAndCleanupRecoveryTimer(const String& devicePath)
00193 {
00194     for (UPInt i = 0, n = RecoveryTimers.GetSize(); i < n; ++i)
00195     {
00196         RecoveryTimerDesc* pdesc = &RecoveryTimers[i];
00197         if (pdesc->DevicePath.CompareNoCase(devicePath))
00198         {
00199             CleanupRecoveryTimer(i);
00200             break;
00201         }
00202     }
00203 }
00204 
00205 LRESULT CALLBACK DeviceStatus::WindowsMessageCallback(  HWND hwnd, 
00206                                                         UINT message, 
00207                                                         WPARAM wParam, 
00208                                                         LPARAM lParam)
00209 {
00210         switch (message)
00211         {
00212         case WM_CREATE:
00213                 {
00214                         // Setup window user data with device status object pointer.
00215                         LPCREATESTRUCT create_struct = reinterpret_cast<LPCREATESTRUCT>(lParam);
00216                         void *lpCreateParam = create_struct->lpCreateParams;
00217                         DeviceStatus *pDeviceStatus = reinterpret_cast<DeviceStatus*>(lpCreateParam);
00218 
00219                         SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pDeviceStatus));
00220                 }
00221                 return 0;       // Return 0 for successfully handled WM_CREATE.
00222 
00223         case WM_DEVICECHANGE:
00224                 {
00225                         WORD loword = LOWORD(wParam);
00226 
00227                         if (loword != DBT_DEVICEARRIVAL &&
00228                                 loword != DBT_DEVICEREMOVECOMPLETE) 
00229                         {
00230                                 // Ignore messages other than device arrive and remove complete 
00231                 // (we're not handling intermediate ones).
00232                                 return TRUE;    // Grant WM_DEVICECHANGE request.
00233                         }
00234 
00235                         DEV_BROADCAST_DEVICEINTERFACE* hdr;
00236                         hdr = (DEV_BROADCAST_DEVICEINTERFACE*) lParam;
00237 
00238                         if (hdr->dbcc_devicetype != DBT_DEVTYP_DEVICEINTERFACE) 
00239                         {
00240                                 // Ignore non interface device messages.
00241                                 return TRUE;    // Grant WM_DEVICECHANGE request.
00242                         }
00243 
00244                         LONG_PTR userData = GetWindowLongPtr(hwnd, GWLP_USERDATA);
00245                         OVR_ASSERT(userData != NULL);
00246 
00247                         // Call callback on device messages object with the device path.
00248                         DeviceStatus* pDeviceStatus = (DeviceStatus*) userData;
00249                         String devicePath(hdr->dbcc_name);
00250 
00251             // check if HID device caused the event...
00252             if (pDeviceStatus->HidGuid == hdr->dbcc_classguid)
00253             {
00254                 // check if recovery timer is already running; stop it and 
00255                 // remove it, if so.
00256                 pDeviceStatus->FindAndCleanupRecoveryTimer(devicePath);
00257 
00258                 if (!pDeviceStatus->MessageCallback(loword, devicePath))
00259                 {
00260                     // hmmm.... unsuccessful
00261                     if (loword == DBT_DEVICEARRIVAL)
00262                     {
00263                         // Windows sometimes may return errors ERROR_SHARING_VIOLATION and
00264                         // ERROR_FILE_NOT_FOUND when trying to open an USB device via
00265                         // CreateFile. Need to start a recovery timer that will try to 
00266                         // re-open the device again.
00267                         OVR_DEBUG_LOG(("Adding failed, recovering through a timer..."));
00268                         UINT_PTR tid = ::SetTimer(hwnd, ++pDeviceStatus->LastTimerId, 
00269                             USBRecoveryTimeInterval, NULL);
00270                         RecoveryTimerDesc rtDesc;
00271                         rtDesc.TimerId = tid;
00272                         rtDesc.DevicePath = devicePath;
00273                         rtDesc.NumAttempts= 0;
00274                         pDeviceStatus->RecoveryTimers.PushBack(rtDesc);
00275                         // wrap around the timer counter, avoid timerId == 0...
00276                         if (pDeviceStatus->LastTimerId + 1 == 0)
00277                             pDeviceStatus->LastTimerId = 0;
00278                     }
00279                 }
00280             }
00281             // Check if Oculus HDMI device was plugged/unplugged, preliminary
00282             // filtering. (is there any way to get GUID? !AB)
00283             //else if (strstr(devicePath.ToCStr(), "DISPLAY#"))
00284             else if (strstr(devicePath.ToCStr(), "#OVR00"))
00285             {
00286                 pDeviceStatus->MessageCallback(loword, devicePath);
00287             }
00288                 }
00289                 return TRUE;    // Grant WM_DEVICECHANGE request.
00290 
00291         case WM_TIMER:
00292                 {
00293                         if (wParam != 0)
00294                         {
00295                                 LONG_PTR userData = GetWindowLongPtr(hwnd, GWLP_USERDATA);
00296                                 OVR_ASSERT(userData != NULL);
00297 
00298                                 // Call callback on device messages object with the device path.
00299                                 DeviceStatus* pDeviceStatus = (DeviceStatus*) userData;
00300 
00301                 // Check if we have recovery timer running (actually, we must be!)
00302                 UPInt rtIndex;
00303                 RecoveryTimerDesc* prtDesc = pDeviceStatus->FindRecoveryTimer(wParam, &rtIndex);
00304                                 if (prtDesc)
00305                                 {
00306                                         if (pDeviceStatus->MessageCallback(DBT_DEVICEARRIVAL, prtDesc->DevicePath))
00307                                         {
00308                         OVR_DEBUG_LOG(("Recovered, adding is successful, cleaning up the timer..."));
00309                         // now it is successful, kill the timer and cleanup
00310                         pDeviceStatus->CleanupRecoveryTimer(rtIndex);
00311                                         }
00312                     else
00313                     {
00314                         if (++prtDesc->NumAttempts >= MaxUSBRecoveryAttempts)
00315                         {
00316                             OVR_DEBUG_LOG(("Failed to recover USB after %d attempts, path = '%s', aborting...",
00317                                 prtDesc->NumAttempts, prtDesc->DevicePath.ToCStr()));
00318                             pDeviceStatus->CleanupRecoveryTimer(rtIndex);
00319                         }
00320                         else
00321                         {
00322                             OVR_DEBUG_LOG(("Failed to recover USB, %d attempts, path = '%s'",
00323                                 prtDesc->NumAttempts, prtDesc->DevicePath.ToCStr()));
00324                         }
00325                     }
00326                                 }
00327                         }
00328                 }
00329                 return 0;
00330 
00331         case WM_CLOSE:
00332                 {
00333                         LONG_PTR userData = GetWindowLongPtr(hwnd, GWLP_USERDATA);
00334                         OVR_ASSERT(userData != NULL);
00335                         DeviceStatus* pDeviceStatus = (DeviceStatus*) userData;
00336                         pDeviceStatus->hMessageWindow = NULL;
00337 
00338                         DestroyWindow(hwnd);
00339                 }
00340                 return 0;       // We processed the WM_CLOSE message.
00341 
00342         case WM_DESTROY:
00343                 PostQuitMessage(0);
00344                 return 0;       // We processed the WM_DESTROY message.
00345         }
00346 
00347         return DefWindowProc(hwnd, message, wParam, lParam);
00348 }
00349 
00350 }} // namespace OVR::Win32


oculus_sdk
Author(s):
autogenerated on Mon Oct 6 2014 03:01:19