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