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(¬ificationFilter, 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 ¬ificationFilter,
|
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
|