oculus1

annotate libovr/Src/win32/OVR_Win32_DeviceStatus.cpp @ 17:cfe4979ab3eb

ops, minor error in the last commit
author John Tsiombikas <nuclear@member.fsf.org>
date Sat, 21 Sep 2013 07:09:48 +0300
parents
children
rev   line source
nuclear@1 1 /************************************************************************************
nuclear@1 2
nuclear@1 3 Filename : OVR_Win32_DeviceStatus.cpp
nuclear@1 4 Content : Win32 implementation of DeviceStatus.
nuclear@1 5 Created : January 24, 2013
nuclear@1 6 Authors : Lee Cooper
nuclear@1 7
nuclear@1 8 Copyright : Copyright 2013 Oculus VR, Inc. All Rights reserved.
nuclear@1 9
nuclear@1 10 Use of this software is subject to the terms of the Oculus license
nuclear@1 11 agreement provided at the time of installation or download, or which
nuclear@1 12 otherwise accompanies this software in either electronic or hard copy form.
nuclear@1 13
nuclear@1 14 *************************************************************************************/
nuclear@1 15
nuclear@1 16 #include "OVR_Win32_DeviceStatus.h"
nuclear@1 17
nuclear@1 18 #include "OVR_Win32_HIDDevice.h"
nuclear@1 19
nuclear@1 20 #include "Kernel/OVR_Log.h"
nuclear@1 21
nuclear@1 22 #include <dbt.h>
nuclear@1 23
nuclear@1 24 namespace OVR { namespace Win32 {
nuclear@1 25
nuclear@1 26 static TCHAR windowClassName[] = TEXT("LibOVR_DeviceStatus_WindowClass");
nuclear@1 27
nuclear@1 28 //-------------------------------------------------------------------------------------
nuclear@1 29 DeviceStatus::DeviceStatus(Notifier* const pClient)
nuclear@1 30 : pNotificationClient(pClient), LastTimerId(0)
nuclear@1 31 {
nuclear@1 32 }
nuclear@1 33
nuclear@1 34 bool DeviceStatus::Initialize()
nuclear@1 35 {
nuclear@1 36
nuclear@1 37 WNDCLASS wndClass;
nuclear@1 38 wndClass.style = CS_HREDRAW | CS_VREDRAW;
nuclear@1 39 wndClass.lpfnWndProc = WindowsMessageCallback;
nuclear@1 40 wndClass.cbClsExtra = 0;
nuclear@1 41 wndClass.cbWndExtra = 0;
nuclear@1 42 wndClass.hInstance = 0;
nuclear@1 43 wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
nuclear@1 44 wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
nuclear@1 45 wndClass.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
nuclear@1 46 wndClass.lpszMenuName = NULL;
nuclear@1 47 wndClass.lpszClassName = windowClassName;
nuclear@1 48
nuclear@1 49 if (!RegisterClass(&wndClass))
nuclear@1 50 {
nuclear@1 51 OVR_ASSERT_LOG(false, ("Failed to register window class."));
nuclear@1 52 return false;
nuclear@1 53 }
nuclear@1 54
nuclear@1 55 // We're going to create a 'message-only' window. This will be hidden, can't be enumerated etc.
nuclear@1 56 // To do this we supply 'HWND_MESSAGE' as the hWndParent.
nuclear@1 57 // http://msdn.microsoft.com/en-us/library/ms632599%28VS.85%29.aspx#message_only
nuclear@1 58 hMessageWindow = CreateWindow( windowClassName,
nuclear@1 59 windowClassName,
nuclear@1 60 WS_OVERLAPPEDWINDOW,
nuclear@1 61 CW_USEDEFAULT,
nuclear@1 62 CW_USEDEFAULT,
nuclear@1 63 CW_USEDEFAULT,
nuclear@1 64 CW_USEDEFAULT,
nuclear@1 65 HWND_MESSAGE,
nuclear@1 66 NULL,
nuclear@1 67 0,
nuclear@1 68 this); // Pass this object via the CREATESTRUCT mechanism
nuclear@1 69 // so that we can attach it to the window user data.
nuclear@1 70
nuclear@1 71 if (hMessageWindow == NULL)
nuclear@1 72 {
nuclear@1 73 OVR_ASSERT_LOG(false, ("Failed to create window."));
nuclear@1 74 return false;
nuclear@1 75 }
nuclear@1 76
nuclear@1 77 // According to MS, topmost windows receive WM_DEVICECHANGE faster.
nuclear@1 78 ::SetWindowPos(hMessageWindow, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
nuclear@1 79 UpdateWindow(hMessageWindow);
nuclear@1 80
nuclear@1 81
nuclear@1 82 // Register notification for additional HID messages.
nuclear@1 83 HIDDeviceManager* hidDeviceManager = new HIDDeviceManager(NULL);
nuclear@1 84 HidGuid = hidDeviceManager->GetHIDGuid();
nuclear@1 85 hidDeviceManager->Release();
nuclear@1 86
nuclear@1 87 DEV_BROADCAST_DEVICEINTERFACE notificationFilter;
nuclear@1 88
nuclear@1 89 ZeroMemory(&notificationFilter, sizeof(notificationFilter));
nuclear@1 90 notificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
nuclear@1 91 notificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
nuclear@1 92 //notificationFilter.dbcc_classguid = hidguid;
nuclear@1 93
nuclear@1 94 // We need DEVICE_NOTIFY_ALL_INTERFACE_CLASSES to detect
nuclear@1 95 // HDMI plug/unplug events.
nuclear@1 96 hDeviceNotify = RegisterDeviceNotification(
nuclear@1 97 hMessageWindow,
nuclear@1 98 &notificationFilter,
nuclear@1 99 DEVICE_NOTIFY_ALL_INTERFACE_CLASSES|DEVICE_NOTIFY_WINDOW_HANDLE);
nuclear@1 100
nuclear@1 101 if (hDeviceNotify == NULL)
nuclear@1 102 {
nuclear@1 103 OVR_ASSERT_LOG(false, ("Failed to register for device notifications."));
nuclear@1 104 return false;
nuclear@1 105 }
nuclear@1 106
nuclear@1 107 return true;
nuclear@1 108 }
nuclear@1 109
nuclear@1 110 void DeviceStatus::ShutDown()
nuclear@1 111 {
nuclear@1 112 OVR_ASSERT(hMessageWindow);
nuclear@1 113
nuclear@1 114 if (!UnregisterDeviceNotification(hDeviceNotify))
nuclear@1 115 {
nuclear@1 116 OVR_ASSERT_LOG(false, ("Failed to unregister device notification."));
nuclear@1 117 }
nuclear@1 118
nuclear@1 119 PostMessage(hMessageWindow, WM_CLOSE, 0, 0);
nuclear@1 120
nuclear@1 121 while (hMessageWindow != NULL)
nuclear@1 122 {
nuclear@1 123 ProcessMessages();
nuclear@1 124 Sleep(1);
nuclear@1 125 }
nuclear@1 126
nuclear@1 127 if (!UnregisterClass(windowClassName, NULL))
nuclear@1 128 {
nuclear@1 129 OVR_ASSERT_LOG(false, ("Failed to unregister window class."));
nuclear@1 130 }
nuclear@1 131 }
nuclear@1 132
nuclear@1 133 DeviceStatus::~DeviceStatus()
nuclear@1 134 {
nuclear@1 135 OVR_ASSERT_LOG(hMessageWindow == NULL, ("Need to call 'ShutDown' from DeviceManagerThread."));
nuclear@1 136 }
nuclear@1 137
nuclear@1 138 void DeviceStatus::ProcessMessages()
nuclear@1 139 {
nuclear@1 140 OVR_ASSERT_LOG(hMessageWindow != NULL, ("Need to call 'Initialize' before first use."));
nuclear@1 141
nuclear@1 142 MSG msg;
nuclear@1 143
nuclear@1 144 // Note WM_DEVICECHANGED messages are dispatched but not retrieved by PeekMessage.
nuclear@1 145 // I think this is because they are pending, non-queued messages.
nuclear@1 146 while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
nuclear@1 147 {
nuclear@1 148 TranslateMessage(&msg);
nuclear@1 149 DispatchMessage(&msg);
nuclear@1 150 }
nuclear@1 151 }
nuclear@1 152
nuclear@1 153 bool DeviceStatus::MessageCallback(WORD messageType, const String& devicePath)
nuclear@1 154 {
nuclear@1 155 bool rv = true;
nuclear@1 156 if (messageType == DBT_DEVICEARRIVAL)
nuclear@1 157 {
nuclear@1 158 rv = pNotificationClient->OnMessage(Notifier::DeviceAdded, devicePath);
nuclear@1 159 }
nuclear@1 160 else if (messageType == DBT_DEVICEREMOVECOMPLETE)
nuclear@1 161 {
nuclear@1 162 pNotificationClient->OnMessage(Notifier::DeviceRemoved, devicePath);
nuclear@1 163 }
nuclear@1 164 else
nuclear@1 165 {
nuclear@1 166 OVR_ASSERT(0);
nuclear@1 167 }
nuclear@1 168 return rv;
nuclear@1 169 }
nuclear@1 170
nuclear@1 171 void DeviceStatus::CleanupRecoveryTimer(UPInt index)
nuclear@1 172 {
nuclear@1 173 ::KillTimer(hMessageWindow, RecoveryTimers[index].TimerId);
nuclear@1 174 RecoveryTimers.RemoveAt(index);
nuclear@1 175 }
nuclear@1 176
nuclear@1 177 DeviceStatus::RecoveryTimerDesc*
nuclear@1 178 DeviceStatus::FindRecoveryTimer(UINT_PTR timerId, UPInt* pindex)
nuclear@1 179 {
nuclear@1 180 for (UPInt i = 0, n = RecoveryTimers.GetSize(); i < n; ++i)
nuclear@1 181 {
nuclear@1 182 RecoveryTimerDesc* pdesc = &RecoveryTimers[i];
nuclear@1 183 if (pdesc->TimerId == timerId)
nuclear@1 184 {
nuclear@1 185 *pindex = i;
nuclear@1 186 return pdesc;
nuclear@1 187 }
nuclear@1 188 }
nuclear@1 189 return NULL;
nuclear@1 190 }
nuclear@1 191
nuclear@1 192 void DeviceStatus::FindAndCleanupRecoveryTimer(const String& devicePath)
nuclear@1 193 {
nuclear@1 194 for (UPInt i = 0, n = RecoveryTimers.GetSize(); i < n; ++i)
nuclear@1 195 {
nuclear@1 196 RecoveryTimerDesc* pdesc = &RecoveryTimers[i];
nuclear@1 197 if (pdesc->DevicePath.CompareNoCase(devicePath))
nuclear@1 198 {
nuclear@1 199 CleanupRecoveryTimer(i);
nuclear@1 200 break;
nuclear@1 201 }
nuclear@1 202 }
nuclear@1 203 }
nuclear@1 204
nuclear@1 205 LRESULT CALLBACK DeviceStatus::WindowsMessageCallback( HWND hwnd,
nuclear@1 206 UINT message,
nuclear@1 207 WPARAM wParam,
nuclear@1 208 LPARAM lParam)
nuclear@1 209 {
nuclear@1 210 switch (message)
nuclear@1 211 {
nuclear@1 212 case WM_CREATE:
nuclear@1 213 {
nuclear@1 214 // Setup window user data with device status object pointer.
nuclear@1 215 LPCREATESTRUCT create_struct = reinterpret_cast<LPCREATESTRUCT>(lParam);
nuclear@1 216 void *lpCreateParam = create_struct->lpCreateParams;
nuclear@1 217 DeviceStatus *pDeviceStatus = reinterpret_cast<DeviceStatus*>(lpCreateParam);
nuclear@1 218
nuclear@1 219 SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pDeviceStatus));
nuclear@1 220 }
nuclear@1 221 return 0; // Return 0 for successfully handled WM_CREATE.
nuclear@1 222
nuclear@1 223 case WM_DEVICECHANGE:
nuclear@1 224 {
nuclear@1 225 WORD loword = LOWORD(wParam);
nuclear@1 226
nuclear@1 227 if (loword != DBT_DEVICEARRIVAL &&
nuclear@1 228 loword != DBT_DEVICEREMOVECOMPLETE)
nuclear@1 229 {
nuclear@1 230 // Ignore messages other than device arrive and remove complete
nuclear@1 231 // (we're not handling intermediate ones).
nuclear@1 232 return TRUE; // Grant WM_DEVICECHANGE request.
nuclear@1 233 }
nuclear@1 234
nuclear@1 235 DEV_BROADCAST_DEVICEINTERFACE* hdr;
nuclear@1 236 hdr = (DEV_BROADCAST_DEVICEINTERFACE*) lParam;
nuclear@1 237
nuclear@1 238 if (hdr->dbcc_devicetype != DBT_DEVTYP_DEVICEINTERFACE)
nuclear@1 239 {
nuclear@1 240 // Ignore non interface device messages.
nuclear@1 241 return TRUE; // Grant WM_DEVICECHANGE request.
nuclear@1 242 }
nuclear@1 243
nuclear@1 244 LONG_PTR userData = GetWindowLongPtr(hwnd, GWLP_USERDATA);
nuclear@1 245 OVR_ASSERT(userData != NULL);
nuclear@1 246
nuclear@1 247 // Call callback on device messages object with the device path.
nuclear@1 248 DeviceStatus* pDeviceStatus = (DeviceStatus*) userData;
nuclear@1 249 String devicePath(hdr->dbcc_name);
nuclear@1 250
nuclear@1 251 // check if HID device caused the event...
nuclear@1 252 if (pDeviceStatus->HidGuid == hdr->dbcc_classguid)
nuclear@1 253 {
nuclear@1 254 // check if recovery timer is already running; stop it and
nuclear@1 255 // remove it, if so.
nuclear@1 256 pDeviceStatus->FindAndCleanupRecoveryTimer(devicePath);
nuclear@1 257
nuclear@1 258 if (!pDeviceStatus->MessageCallback(loword, devicePath))
nuclear@1 259 {
nuclear@1 260 // hmmm.... unsuccessful
nuclear@1 261 if (loword == DBT_DEVICEARRIVAL)
nuclear@1 262 {
nuclear@1 263 // Windows sometimes may return errors ERROR_SHARING_VIOLATION and
nuclear@1 264 // ERROR_FILE_NOT_FOUND when trying to open an USB device via
nuclear@1 265 // CreateFile. Need to start a recovery timer that will try to
nuclear@1 266 // re-open the device again.
nuclear@1 267 OVR_DEBUG_LOG(("Adding failed, recovering through a timer..."));
nuclear@1 268 UINT_PTR tid = ::SetTimer(hwnd, ++pDeviceStatus->LastTimerId,
nuclear@1 269 USBRecoveryTimeInterval, NULL);
nuclear@1 270 RecoveryTimerDesc rtDesc;
nuclear@1 271 rtDesc.TimerId = tid;
nuclear@1 272 rtDesc.DevicePath = devicePath;
nuclear@1 273 rtDesc.NumAttempts= 0;
nuclear@1 274 pDeviceStatus->RecoveryTimers.PushBack(rtDesc);
nuclear@1 275 // wrap around the timer counter, avoid timerId == 0...
nuclear@1 276 if (pDeviceStatus->LastTimerId + 1 == 0)
nuclear@1 277 pDeviceStatus->LastTimerId = 0;
nuclear@1 278 }
nuclear@1 279 }
nuclear@1 280 }
nuclear@1 281 // Check if Oculus HDMI device was plugged/unplugged, preliminary
nuclear@1 282 // filtering. (is there any way to get GUID? !AB)
nuclear@1 283 //else if (strstr(devicePath.ToCStr(), "DISPLAY#"))
nuclear@1 284 else if (strstr(devicePath.ToCStr(), "#OVR00"))
nuclear@1 285 {
nuclear@1 286 pDeviceStatus->MessageCallback(loword, devicePath);
nuclear@1 287 }
nuclear@1 288 }
nuclear@1 289 return TRUE; // Grant WM_DEVICECHANGE request.
nuclear@1 290
nuclear@1 291 case WM_TIMER:
nuclear@1 292 {
nuclear@1 293 if (wParam != 0)
nuclear@1 294 {
nuclear@1 295 LONG_PTR userData = GetWindowLongPtr(hwnd, GWLP_USERDATA);
nuclear@1 296 OVR_ASSERT(userData != NULL);
nuclear@1 297
nuclear@1 298 // Call callback on device messages object with the device path.
nuclear@1 299 DeviceStatus* pDeviceStatus = (DeviceStatus*) userData;
nuclear@1 300
nuclear@1 301 // Check if we have recovery timer running (actually, we must be!)
nuclear@1 302 UPInt rtIndex;
nuclear@1 303 RecoveryTimerDesc* prtDesc = pDeviceStatus->FindRecoveryTimer(wParam, &rtIndex);
nuclear@1 304 if (prtDesc)
nuclear@1 305 {
nuclear@1 306 if (pDeviceStatus->MessageCallback(DBT_DEVICEARRIVAL, prtDesc->DevicePath))
nuclear@1 307 {
nuclear@1 308 OVR_DEBUG_LOG(("Recovered, adding is successful, cleaning up the timer..."));
nuclear@1 309 // now it is successful, kill the timer and cleanup
nuclear@1 310 pDeviceStatus->CleanupRecoveryTimer(rtIndex);
nuclear@1 311 }
nuclear@1 312 else
nuclear@1 313 {
nuclear@1 314 if (++prtDesc->NumAttempts >= MaxUSBRecoveryAttempts)
nuclear@1 315 {
nuclear@1 316 OVR_DEBUG_LOG(("Failed to recover USB after %d attempts, path = '%s', aborting...",
nuclear@1 317 prtDesc->NumAttempts, prtDesc->DevicePath.ToCStr()));
nuclear@1 318 pDeviceStatus->CleanupRecoveryTimer(rtIndex);
nuclear@1 319 }
nuclear@1 320 else
nuclear@1 321 {
nuclear@1 322 OVR_DEBUG_LOG(("Failed to recover USB, %d attempts, path = '%s'",
nuclear@1 323 prtDesc->NumAttempts, prtDesc->DevicePath.ToCStr()));
nuclear@1 324 }
nuclear@1 325 }
nuclear@1 326 }
nuclear@1 327 }
nuclear@1 328 }
nuclear@1 329 return 0;
nuclear@1 330
nuclear@1 331 case WM_CLOSE:
nuclear@1 332 {
nuclear@1 333 LONG_PTR userData = GetWindowLongPtr(hwnd, GWLP_USERDATA);
nuclear@1 334 OVR_ASSERT(userData != NULL);
nuclear@1 335 DeviceStatus* pDeviceStatus = (DeviceStatus*) userData;
nuclear@1 336 pDeviceStatus->hMessageWindow = NULL;
nuclear@1 337
nuclear@1 338 DestroyWindow(hwnd);
nuclear@1 339 }
nuclear@1 340 return 0; // We processed the WM_CLOSE message.
nuclear@1 341
nuclear@1 342 case WM_DESTROY:
nuclear@1 343 PostQuitMessage(0);
nuclear@1 344 return 0; // We processed the WM_DESTROY message.
nuclear@1 345 }
nuclear@1 346
nuclear@1 347 return DefWindowProc(hwnd, message, wParam, lParam);
nuclear@1 348 }
nuclear@1 349
nuclear@1 350 }} // namespace OVR::Win32