nuclear@1: /************************************************************************************ nuclear@1: nuclear@1: Filename : OVR_Win32_DeviceStatus.cpp nuclear@1: Content : Win32 implementation of DeviceStatus. nuclear@1: Created : January 24, 2013 nuclear@1: Authors : Lee Cooper nuclear@1: nuclear@1: Copyright : Copyright 2013 Oculus VR, Inc. All Rights reserved. nuclear@1: nuclear@1: Use of this software is subject to the terms of the Oculus license nuclear@1: agreement provided at the time of installation or download, or which nuclear@1: otherwise accompanies this software in either electronic or hard copy form. nuclear@1: nuclear@1: *************************************************************************************/ nuclear@1: nuclear@1: #include "OVR_Win32_DeviceStatus.h" nuclear@1: nuclear@1: #include "OVR_Win32_HIDDevice.h" nuclear@1: nuclear@1: #include "Kernel/OVR_Log.h" nuclear@1: nuclear@1: #include nuclear@1: nuclear@1: namespace OVR { namespace Win32 { nuclear@1: nuclear@1: static TCHAR windowClassName[] = TEXT("LibOVR_DeviceStatus_WindowClass"); nuclear@1: nuclear@1: //------------------------------------------------------------------------------------- nuclear@1: DeviceStatus::DeviceStatus(Notifier* const pClient) nuclear@1: : pNotificationClient(pClient), LastTimerId(0) nuclear@1: { nuclear@1: } nuclear@1: nuclear@1: bool DeviceStatus::Initialize() nuclear@1: { nuclear@1: nuclear@1: WNDCLASS wndClass; nuclear@1: wndClass.style = CS_HREDRAW | CS_VREDRAW; nuclear@1: wndClass.lpfnWndProc = WindowsMessageCallback; nuclear@1: wndClass.cbClsExtra = 0; nuclear@1: wndClass.cbWndExtra = 0; nuclear@1: wndClass.hInstance = 0; nuclear@1: wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION); nuclear@1: wndClass.hCursor = LoadCursor(NULL, IDC_ARROW); nuclear@1: wndClass.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); nuclear@1: wndClass.lpszMenuName = NULL; nuclear@1: wndClass.lpszClassName = windowClassName; nuclear@1: nuclear@1: if (!RegisterClass(&wndClass)) nuclear@1: { nuclear@1: OVR_ASSERT_LOG(false, ("Failed to register window class.")); nuclear@1: return false; nuclear@1: } nuclear@1: nuclear@1: // We're going to create a 'message-only' window. This will be hidden, can't be enumerated etc. nuclear@1: // To do this we supply 'HWND_MESSAGE' as the hWndParent. nuclear@1: // http://msdn.microsoft.com/en-us/library/ms632599%28VS.85%29.aspx#message_only nuclear@1: hMessageWindow = CreateWindow( windowClassName, nuclear@1: windowClassName, nuclear@1: WS_OVERLAPPEDWINDOW, nuclear@1: CW_USEDEFAULT, nuclear@1: CW_USEDEFAULT, nuclear@1: CW_USEDEFAULT, nuclear@1: CW_USEDEFAULT, nuclear@1: HWND_MESSAGE, nuclear@1: NULL, nuclear@1: 0, nuclear@1: this); // Pass this object via the CREATESTRUCT mechanism nuclear@1: // so that we can attach it to the window user data. nuclear@1: nuclear@1: if (hMessageWindow == NULL) nuclear@1: { nuclear@1: OVR_ASSERT_LOG(false, ("Failed to create window.")); nuclear@1: return false; nuclear@1: } nuclear@1: nuclear@1: // According to MS, topmost windows receive WM_DEVICECHANGE faster. nuclear@1: ::SetWindowPos(hMessageWindow, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE); nuclear@1: UpdateWindow(hMessageWindow); nuclear@1: nuclear@1: nuclear@1: // Register notification for additional HID messages. nuclear@1: HIDDeviceManager* hidDeviceManager = new HIDDeviceManager(NULL); nuclear@1: HidGuid = hidDeviceManager->GetHIDGuid(); nuclear@1: hidDeviceManager->Release(); nuclear@1: nuclear@1: DEV_BROADCAST_DEVICEINTERFACE notificationFilter; nuclear@1: nuclear@1: ZeroMemory(¬ificationFilter, sizeof(notificationFilter)); nuclear@1: notificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE); nuclear@1: notificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; nuclear@1: //notificationFilter.dbcc_classguid = hidguid; nuclear@1: nuclear@1: // We need DEVICE_NOTIFY_ALL_INTERFACE_CLASSES to detect nuclear@1: // HDMI plug/unplug events. nuclear@1: hDeviceNotify = RegisterDeviceNotification( nuclear@1: hMessageWindow, nuclear@1: ¬ificationFilter, nuclear@1: DEVICE_NOTIFY_ALL_INTERFACE_CLASSES|DEVICE_NOTIFY_WINDOW_HANDLE); nuclear@1: nuclear@1: if (hDeviceNotify == NULL) nuclear@1: { nuclear@1: OVR_ASSERT_LOG(false, ("Failed to register for device notifications.")); nuclear@1: return false; nuclear@1: } nuclear@1: nuclear@1: return true; nuclear@1: } nuclear@1: nuclear@1: void DeviceStatus::ShutDown() nuclear@1: { nuclear@1: OVR_ASSERT(hMessageWindow); nuclear@1: nuclear@1: if (!UnregisterDeviceNotification(hDeviceNotify)) nuclear@1: { nuclear@1: OVR_ASSERT_LOG(false, ("Failed to unregister device notification.")); nuclear@1: } nuclear@1: nuclear@1: PostMessage(hMessageWindow, WM_CLOSE, 0, 0); nuclear@1: nuclear@1: while (hMessageWindow != NULL) nuclear@1: { nuclear@1: ProcessMessages(); nuclear@1: Sleep(1); nuclear@1: } nuclear@1: nuclear@1: if (!UnregisterClass(windowClassName, NULL)) nuclear@1: { nuclear@1: OVR_ASSERT_LOG(false, ("Failed to unregister window class.")); nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: DeviceStatus::~DeviceStatus() nuclear@1: { nuclear@1: OVR_ASSERT_LOG(hMessageWindow == NULL, ("Need to call 'ShutDown' from DeviceManagerThread.")); nuclear@1: } nuclear@1: nuclear@1: void DeviceStatus::ProcessMessages() nuclear@1: { nuclear@1: OVR_ASSERT_LOG(hMessageWindow != NULL, ("Need to call 'Initialize' before first use.")); nuclear@1: nuclear@1: MSG msg; nuclear@1: nuclear@1: // Note WM_DEVICECHANGED messages are dispatched but not retrieved by PeekMessage. nuclear@1: // I think this is because they are pending, non-queued messages. nuclear@1: while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) nuclear@1: { nuclear@1: TranslateMessage(&msg); nuclear@1: DispatchMessage(&msg); nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: bool DeviceStatus::MessageCallback(WORD messageType, const String& devicePath) nuclear@1: { nuclear@1: bool rv = true; nuclear@1: if (messageType == DBT_DEVICEARRIVAL) nuclear@1: { nuclear@1: rv = pNotificationClient->OnMessage(Notifier::DeviceAdded, devicePath); nuclear@1: } nuclear@1: else if (messageType == DBT_DEVICEREMOVECOMPLETE) nuclear@1: { nuclear@1: pNotificationClient->OnMessage(Notifier::DeviceRemoved, devicePath); nuclear@1: } nuclear@1: else nuclear@1: { nuclear@1: OVR_ASSERT(0); nuclear@1: } nuclear@1: return rv; nuclear@1: } nuclear@1: nuclear@1: void DeviceStatus::CleanupRecoveryTimer(UPInt index) nuclear@1: { nuclear@1: ::KillTimer(hMessageWindow, RecoveryTimers[index].TimerId); nuclear@1: RecoveryTimers.RemoveAt(index); nuclear@1: } nuclear@1: nuclear@1: DeviceStatus::RecoveryTimerDesc* nuclear@1: DeviceStatus::FindRecoveryTimer(UINT_PTR timerId, UPInt* pindex) nuclear@1: { nuclear@1: for (UPInt i = 0, n = RecoveryTimers.GetSize(); i < n; ++i) nuclear@1: { nuclear@1: RecoveryTimerDesc* pdesc = &RecoveryTimers[i]; nuclear@1: if (pdesc->TimerId == timerId) nuclear@1: { nuclear@1: *pindex = i; nuclear@1: return pdesc; nuclear@1: } nuclear@1: } nuclear@1: return NULL; nuclear@1: } nuclear@1: nuclear@1: void DeviceStatus::FindAndCleanupRecoveryTimer(const String& devicePath) nuclear@1: { nuclear@1: for (UPInt i = 0, n = RecoveryTimers.GetSize(); i < n; ++i) nuclear@1: { nuclear@1: RecoveryTimerDesc* pdesc = &RecoveryTimers[i]; nuclear@1: if (pdesc->DevicePath.CompareNoCase(devicePath)) nuclear@1: { nuclear@1: CleanupRecoveryTimer(i); nuclear@1: break; nuclear@1: } nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: LRESULT CALLBACK DeviceStatus::WindowsMessageCallback( HWND hwnd, nuclear@1: UINT message, nuclear@1: WPARAM wParam, nuclear@1: LPARAM lParam) nuclear@1: { nuclear@1: switch (message) nuclear@1: { nuclear@1: case WM_CREATE: nuclear@1: { nuclear@1: // Setup window user data with device status object pointer. nuclear@1: LPCREATESTRUCT create_struct = reinterpret_cast(lParam); nuclear@1: void *lpCreateParam = create_struct->lpCreateParams; nuclear@1: DeviceStatus *pDeviceStatus = reinterpret_cast(lpCreateParam); nuclear@1: nuclear@1: SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast(pDeviceStatus)); nuclear@1: } nuclear@1: return 0; // Return 0 for successfully handled WM_CREATE. nuclear@1: nuclear@1: case WM_DEVICECHANGE: nuclear@1: { nuclear@1: WORD loword = LOWORD(wParam); nuclear@1: nuclear@1: if (loword != DBT_DEVICEARRIVAL && nuclear@1: loword != DBT_DEVICEREMOVECOMPLETE) nuclear@1: { nuclear@1: // Ignore messages other than device arrive and remove complete nuclear@1: // (we're not handling intermediate ones). nuclear@1: return TRUE; // Grant WM_DEVICECHANGE request. nuclear@1: } nuclear@1: nuclear@1: DEV_BROADCAST_DEVICEINTERFACE* hdr; nuclear@1: hdr = (DEV_BROADCAST_DEVICEINTERFACE*) lParam; nuclear@1: nuclear@1: if (hdr->dbcc_devicetype != DBT_DEVTYP_DEVICEINTERFACE) nuclear@1: { nuclear@1: // Ignore non interface device messages. nuclear@1: return TRUE; // Grant WM_DEVICECHANGE request. nuclear@1: } nuclear@1: nuclear@1: LONG_PTR userData = GetWindowLongPtr(hwnd, GWLP_USERDATA); nuclear@1: OVR_ASSERT(userData != NULL); nuclear@1: nuclear@1: // Call callback on device messages object with the device path. nuclear@1: DeviceStatus* pDeviceStatus = (DeviceStatus*) userData; nuclear@1: String devicePath(hdr->dbcc_name); nuclear@1: nuclear@1: // check if HID device caused the event... nuclear@1: if (pDeviceStatus->HidGuid == hdr->dbcc_classguid) nuclear@1: { nuclear@1: // check if recovery timer is already running; stop it and nuclear@1: // remove it, if so. nuclear@1: pDeviceStatus->FindAndCleanupRecoveryTimer(devicePath); nuclear@1: nuclear@1: if (!pDeviceStatus->MessageCallback(loword, devicePath)) nuclear@1: { nuclear@1: // hmmm.... unsuccessful nuclear@1: if (loword == DBT_DEVICEARRIVAL) nuclear@1: { nuclear@1: // Windows sometimes may return errors ERROR_SHARING_VIOLATION and nuclear@1: // ERROR_FILE_NOT_FOUND when trying to open an USB device via nuclear@1: // CreateFile. Need to start a recovery timer that will try to nuclear@1: // re-open the device again. nuclear@1: OVR_DEBUG_LOG(("Adding failed, recovering through a timer...")); nuclear@1: UINT_PTR tid = ::SetTimer(hwnd, ++pDeviceStatus->LastTimerId, nuclear@1: USBRecoveryTimeInterval, NULL); nuclear@1: RecoveryTimerDesc rtDesc; nuclear@1: rtDesc.TimerId = tid; nuclear@1: rtDesc.DevicePath = devicePath; nuclear@1: rtDesc.NumAttempts= 0; nuclear@1: pDeviceStatus->RecoveryTimers.PushBack(rtDesc); nuclear@1: // wrap around the timer counter, avoid timerId == 0... nuclear@1: if (pDeviceStatus->LastTimerId + 1 == 0) nuclear@1: pDeviceStatus->LastTimerId = 0; nuclear@1: } nuclear@1: } nuclear@1: } nuclear@1: // Check if Oculus HDMI device was plugged/unplugged, preliminary nuclear@1: // filtering. (is there any way to get GUID? !AB) nuclear@1: //else if (strstr(devicePath.ToCStr(), "DISPLAY#")) nuclear@1: else if (strstr(devicePath.ToCStr(), "#OVR00")) nuclear@1: { nuclear@1: pDeviceStatus->MessageCallback(loword, devicePath); nuclear@1: } nuclear@1: } nuclear@1: return TRUE; // Grant WM_DEVICECHANGE request. nuclear@1: nuclear@1: case WM_TIMER: nuclear@1: { nuclear@1: if (wParam != 0) nuclear@1: { nuclear@1: LONG_PTR userData = GetWindowLongPtr(hwnd, GWLP_USERDATA); nuclear@1: OVR_ASSERT(userData != NULL); nuclear@1: nuclear@1: // Call callback on device messages object with the device path. nuclear@1: DeviceStatus* pDeviceStatus = (DeviceStatus*) userData; nuclear@1: nuclear@1: // Check if we have recovery timer running (actually, we must be!) nuclear@1: UPInt rtIndex; nuclear@1: RecoveryTimerDesc* prtDesc = pDeviceStatus->FindRecoveryTimer(wParam, &rtIndex); nuclear@1: if (prtDesc) nuclear@1: { nuclear@1: if (pDeviceStatus->MessageCallback(DBT_DEVICEARRIVAL, prtDesc->DevicePath)) nuclear@1: { nuclear@1: OVR_DEBUG_LOG(("Recovered, adding is successful, cleaning up the timer...")); nuclear@1: // now it is successful, kill the timer and cleanup nuclear@1: pDeviceStatus->CleanupRecoveryTimer(rtIndex); nuclear@1: } nuclear@1: else nuclear@1: { nuclear@1: if (++prtDesc->NumAttempts >= MaxUSBRecoveryAttempts) nuclear@1: { nuclear@1: OVR_DEBUG_LOG(("Failed to recover USB after %d attempts, path = '%s', aborting...", nuclear@1: prtDesc->NumAttempts, prtDesc->DevicePath.ToCStr())); nuclear@1: pDeviceStatus->CleanupRecoveryTimer(rtIndex); nuclear@1: } nuclear@1: else nuclear@1: { nuclear@1: OVR_DEBUG_LOG(("Failed to recover USB, %d attempts, path = '%s'", nuclear@1: prtDesc->NumAttempts, prtDesc->DevicePath.ToCStr())); nuclear@1: } nuclear@1: } nuclear@1: } nuclear@1: } nuclear@1: } nuclear@1: return 0; nuclear@1: nuclear@1: case WM_CLOSE: nuclear@1: { nuclear@1: LONG_PTR userData = GetWindowLongPtr(hwnd, GWLP_USERDATA); nuclear@1: OVR_ASSERT(userData != NULL); nuclear@1: DeviceStatus* pDeviceStatus = (DeviceStatus*) userData; nuclear@1: pDeviceStatus->hMessageWindow = NULL; nuclear@1: nuclear@1: DestroyWindow(hwnd); nuclear@1: } nuclear@1: return 0; // We processed the WM_CLOSE message. nuclear@1: nuclear@1: case WM_DESTROY: nuclear@1: PostQuitMessage(0); nuclear@1: return 0; // We processed the WM_DESTROY message. nuclear@1: } nuclear@1: nuclear@1: return DefWindowProc(hwnd, message, wParam, lParam); nuclear@1: } nuclear@1: nuclear@1: }} // namespace OVR::Win32