vrpn 07.35
Virtual Reality Peripheral Network
Loading...
Searching...
No Matches
vrpn_DirectXRumblePad.C
Go to the documentation of this file.
1// vrpn_DirectXRumblePad.C: This is another driver for force-feedback
2// joysticks under Windows such as the Logitech Cordless RumblePad.
3//
4// Note that if you ARE using a Logitech gamepad, it is almost a
5// certainty that this code will not work without Logitech's drivers
6// installed. Without them you will not be able to find any available
7// force feedback effects, though polling joystick axes and the POV hat
8// should work fine.
9//
10// Chris VanderKnyff, revised July 2007
11
13
14#if defined(_WIN32) && defined(VRPN_USE_DIRECTINPUT)
15#include <stdio.h>
16#include <time.h> // for time
17#include <algorithm> // for min
18using std::min;
19
20// Hacks to maintain VC++6 compatibility while preserving 64-bitness
21// Also cleans up the annoying (and in this case meaningless) warnings under Win32
22#ifdef _WIN64
23# if !defined(GetWindowLongPtr) || !defined(SetWindowLongPtr)
24# error 64-bit compilation requires an SDK that supports LONG_PTRs.
25# endif
26#else
27# ifndef LONG_PTR
28# define LONG_PTR LONG
29# endif
30# ifdef GetWindowLongPtr
31# undef GetWindowLongPtr
32# undef GWLP_USERDATA
33# endif
34# ifdef SetWindowLongPtr
35# undef SetWindowLongPtr
36# endif
37# define GetWindowLongPtr GetWindowLong
38# define SetWindowLongPtr SetWindowLong
39# define GWLP_USERDATA GWL_USERDATA
40#endif
41#ifndef HWND_MESSAGE
42# define HWND_MESSAGE ((HWND) -3)
43#endif
44
45// __ImageBase allows us to get the HINSTANCE for the specific component
46// we've been compiled into. This is actually the correct way for static
47// libraries, because GetModuleHandle(NULL) returns the HINSTANCE for the
48// hosting EXE. In the case where VRPN is a DLL itself or has been linked
49// into a DLL, GetModuleHandle(NULL) is the wrong process's HINSTANCE.
50//
51// By using the pseudo-variable __ImageBase provided by the Visual C++
52// compiler, we know exactly what our proper HINSTANCE is.
53//
54// More info: http://blogs.msdn.com/oldnewthing/archive/2004/10/25/247180.aspx
55EXTERN_C IMAGE_DOS_HEADER __ImageBase;
56#define HINST_THISCOMPONENT ((HINSTANCE)&__ImageBase)
57
58namespace {
59 const char *CLASS_NAME = "vrpn_DirectXRumblePad"; // Win32 window class name
60 const int MSG_POLL = WM_APP + 101; // app-specific "polling" message
61 const int MSG_SETMAGNITUDE = WM_APP + 102; // app-specific "set magnitude" message
62 const int POLL_SUCCESS = 1701; // private value to indicate polling success
63
64 // Quick-and-dirty GUID comparison function
65 // Arguments: Two Win32 GUID structures
66 // Returns: true iff the two GUIDs are identical
67 inline bool guid_equals(const GUID &a, const GUID &b) {
68 return !memcmp(&a, &b, sizeof(GUID));
69 }
70}
71
72// Device constructor.
73// Parameters:
74// - name: VRPN name to assign to this server
75// - c: VRPN connection this device should be attached to
76// - device_guid: If provided, the specific DirectInput device GUID we want to use
77// If not provided (or the null GUID), we'll bind to the first available joystick
78vrpn_DirectXRumblePad::vrpn_DirectXRumblePad(const char *name, vrpn_Connection *c,
79 GUID device_guid)
80 : vrpn_Analog(name, c)
81 , vrpn_Button_Filter(name, c)
82 , vrpn_Analog_Output(name, c)
83 , _target_device(device_guid)
84 , _wnd(NULL)
85 , _thread(INVALID_HANDLE_VALUE)
86 , _directInput(NULL)
87 , _effect(NULL)
88 , _gamepad(NULL)
89{
90 last_error = time(NULL);
91
95
96 // Register a handler for the request channel change message
97 if (register_autodeleted_handler(request_m_id,
98 handle_request_message, this, d_sender_id)) {
99 FAIL("vrpn_DirectXRumblePad: can't register change channel request handler");
100 return;
101 }
102
103 // Register a handler for the request channels change message
104 if (register_autodeleted_handler(request_channels_m_id,
105 handle_request_channels_message, this, d_sender_id)) {
106 FAIL("vrpn_DirectXRumblePad: can't register change channels request handler");
107 return;
108 }
109
110 // Register a handler for the no-one's-connected-now message
111 if (register_autodeleted_handler(
112 d_connection->register_message_type(vrpn_dropped_last_connection),
113 handle_last_connection_dropped, this)) {
114 FAIL("Can't register self-destruct handler");
115 return;
116 }
117
118 _thread = CreateThread(NULL, 0, thread_proc, this, 0, NULL);
119 if (_thread == INVALID_HANDLE_VALUE) {
120 FAIL("Unable to create thread.");
121 return;
122 }
123}
124
125// Device destructor
126vrpn_DirectXRumblePad::~vrpn_DirectXRumblePad() {
127 // Tell the thread window to close itself
128 if (_wnd) {
129 SendMessage(_wnd, WM_CLOSE, 0, 0);
130 }
131
132 // Wait for the worker thread to terminate
133 // Spinlocks are suboptimal, but I know no easy way to "join" to a thread's termination.
134 if (_thread != INVALID_HANDLE_VALUE) {
135 DWORD code;
136 do {
137 GetExitCodeThread(_thread, &code);
138 } while (code == STILL_ACTIVE);
139 }
140}
141
142// joystick_enum_cb: Callback function for joystick enumeration
143// Tries to find the desired device GUID if provided.
144// Otherwise it will match on the first joystick enumerated.
145BOOL CALLBACK vrpn_DirectXRumblePad::joystick_enum_cb(LPCDIDEVICEINSTANCE lpddi, LPVOID ref) {
146 vrpn_DirectXRumblePad *me = reinterpret_cast<vrpn_DirectXRumblePad *>(ref);
147
148 if (guid_equals(me->_target_device, GUID_NULL)
149 || guid_equals(me->_target_device, lpddi->guidInstance)) {
150 if (SUCCEEDED(me->_directInput->CreateDevice(lpddi->guidInstance, &me->_gamepad, 0))) {
151 return DIENUM_STOP;
152 }
153 else {
154 me->FAIL("Unable to create device instance! Attempting to find another one.");
155 return DIENUM_CONTINUE;
156 }
157 }
158 return DIENUM_CONTINUE;
159}
160
161// Main thread procedure. Registers a window class, creates a worker window
162// (so we have an HWND for DirectInput), and runs a message pump.
163// We move all this onto another thread to prevent the host application
164// from accidentally being flagged as a GUI thread. Enough DirectX things
165// have thread affinity that it's safer to isolate things this way.
166// All userland APIs are just wrappers around SendMessage to make sure all
167// DirectX calls are executed in the context of the proper thread.
168//
169// LPVOID parameter is the generic "user data" passed to CreateThread API.
170// In this case, it's our C++ this pointer--everything is static in the other thread.
171// Multiple vrpn_DirectXRumblePads simply create multiple threads with their own local storage.
172DWORD CALLBACK vrpn_DirectXRumblePad::thread_proc(LPVOID ref) {
173 // Retrieve C++ this pointer
174 vrpn_DirectXRumblePad *me = reinterpret_cast<vrpn_DirectXRumblePad *>(ref);
175
176 // Register a simple window class
177 WNDCLASS wc = {0};
178 wc.lpfnWndProc = window_proc;
179 wc.cbWndExtra = sizeof(vrpn_DirectXRumblePad *);
180 wc.hInstance = HINST_THISCOMPONENT;
181 wc.lpszClassName = CLASS_NAME;
182
183 if (!RegisterClass(&wc)) {
184 me->FAIL("Unable to register class.");
185 return 1;
186 }
187
188 // Create our window
189 me->_wnd = CreateWindow(CLASS_NAME, "VRPN Worker Thread", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, ref);
190
191 if (!me->_wnd) {
192 me->FAIL("Unable to create window.");
193 return 1;
194 }
195
196 // Fire up DirectInput
197 if (FAILED(DirectInput8Create(HINST_THISCOMPONENT, DIRECTINPUT_VERSION,
198 IID_IDirectInput8, (void**)&me->_directInput, NULL))) {
199 me->FAIL("Unable to connect DirectInput.");
200 return 1;
201 }
202
203 // Enumerate force-feedback joysticks
204 if (FAILED(me->_directInput->EnumDevices(DI8DEVCLASS_GAMECTRL,
205 joystick_enum_cb, me, DIEDFL_ATTACHEDONLY | DIEDFL_FORCEFEEDBACK))) {
206 me->FAIL("Unable to enumerate joysticks.");
207 return 1;
208 }
209
210 // Make sure we found something
211 if (!me->_gamepad) {
212 me->FAIL("No compatible joystick found!");
213 return 1;
214 }
215
216 // Set data format for retrieving axis values
217 if (FAILED(me->_gamepad->SetDataFormat(&c_dfDIJoystick2))) {
218 me->FAIL("Unable to set data format.");
219 return 1;
220 }
221
222 // Grab exclusive access to the joystick
223 if (FAILED(me->_gamepad->SetCooperativeLevel(me->_wnd, DISCL_EXCLUSIVE | DISCL_BACKGROUND))) {
224 me->FAIL("Unable to set cooperative level.");
225 return 1;
226 }
227
228 // Find all the axes and set their ranges to [-1000, 1000]
229 if (FAILED(me->_gamepad->EnumObjects(axis_enum_cb, me->_gamepad, DIDFT_AXIS))) {
230 me->FAIL("Unable to enumerate axes.");
231 return 1;
232 }
233
234 // Load the force effect onto the joystick
235 if (FAILED(me->init_force())) {
236 me->FAIL("Unable to initialize forces.");
237 return 1;
238 }
239
240 // Acquire the joystick for polling
241 if (FAILED(me->_gamepad->Acquire())) {
242 me->FAIL("Unable to acquire joystick.");
243 return 1;
244 }
245
246 // Start the main message loop
247 BOOL ret;
248 MSG msg;
249 while ((ret = GetMessage(&msg, me->_wnd, 0, 0)) != 0) {
250 if (ret == -1) {
251 me->FAIL("GetMessage() threw an error.");
252 return 1;
253 }
254 else {
255 TranslateMessage(&msg);
256 DispatchMessage(&msg);
257 }
258 }
259
260 // Clean up after ourselves
261 if (me->_effect)
262 me->_effect->Release();
263 if (me->_gamepad)
264 me->_gamepad->Release();
265 if (me->_directInput)
266 me->_directInput->Release();
267
268 return 0;
269}
270
271// Window procedure for the worker thread's helper window
272// Standard WindowProc semantics for a message-only window
273LRESULT CALLBACK vrpn_DirectXRumblePad::window_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
274 // Retrieve "this" pointer
275 // Guaranteed to be garbage until WM_CREATE finishes, but
276 // we don't actually use this value until WM_CREATE writes a valid one
277 vrpn_DirectXRumblePad *me = reinterpret_cast<vrpn_DirectXRumblePad *>
278 (GetWindowLongPtr(hwnd, GWLP_USERDATA));
279
280 switch (msg) {
281 // Window is being created; store "this" pointer for future retrieval
282 case WM_CREATE: {
283 CREATESTRUCT *s = reinterpret_cast<CREATESTRUCT *>(lp);
284 me = reinterpret_cast<vrpn_DirectXRumblePad *>(s->lpCreateParams);
285 SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(me));
286 return 0;
287 }
288
289 // Something (most likely ~vrpn_DirectXRumblePad) wants to close the window
290 // Go ahead and signal shutdown
291 case WM_CLOSE:
292 DestroyWindow(hwnd);
293 PostQuitMessage(0);
294 break;
295
296 // Main thread wants to poll the joystick. Do so.
297 case MSG_POLL:
298 if (FAILED(me->_gamepad->Poll())) {
299 // Keep trying to acquire the joystick if necessary
300 do {
301 me->_gamepad->Acquire();
302 } while (me->_gamepad->Poll() == DIERR_INPUTLOST);
303 }
304 // Read data from the joystick
305 if (SUCCEEDED(me->_gamepad->GetDeviceState(sizeof(DIJOYSTATE2), reinterpret_cast<DIJOYSTATE2 *>(lp)))) {
306 return POLL_SUCCESS; // Make sure main thread knows we're running here
307 }
308 else {
309 me->FAIL("GetDeviceState() returned error result.");
310 break;
311 }
312
313 // Main thread wants to change rumble magnitude. Do so.
314 case MSG_SETMAGNITUDE: {
315 float mag = *reinterpret_cast<float*>(&wp);
316
317 if (me->_effect)
318 me->_effect->Stop();
319
320 if (mag > 0) {
321 HRESULT hr;
322
323 me->_diPeriodic.dwMagnitude = (DWORD) (DI_FFNOMINALMAX * mag);
324 hr = me->_effect->SetParameters(&me->_diEffect, DIEP_TYPESPECIFICPARAMS);
325 hr = me->_effect->Download();
326 hr = me->_effect->Start(1, 0);
327 hr = hr;
328 }
329 break;
330 }
331
332 // Everything not explicitly handled goes to DefWindowProc as per usual
333 default:
334 return DefWindowProc(hwnd, msg, wp, lp);
335 }
336
337 return 0;
338}
339
340// Axis enumeration callback
341// For each joystick axis we can find, tell it to report values in [-1000, 1000]
342BOOL CALLBACK vrpn_DirectXRumblePad::axis_enum_cb(LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID ref) {
343 LPDIRECTINPUTDEVICE8 gamepad = reinterpret_cast<LPDIRECTINPUTDEVICE8>(ref);
344
345 DIPROPRANGE prop;
346 prop.diph.dwSize = sizeof(DIPROPRANGE);
347 prop.diph.dwHeaderSize = sizeof(DIPROPHEADER);
348 prop.diph.dwHow = DIPH_BYID;
349 prop.diph.dwObj = lpddoi->dwType;
350 prop.lMin = -1000;
351 prop.lMax = 1000;
352
353 if (FAILED(gamepad->SetProperty(DIPROP_RANGE, &prop.diph))) {
354 return DIENUM_STOP;
355 }
356
357 return DIENUM_CONTINUE;
358}
359
360// VRPN main loop
361// Poll the joystick, update the axes, and let the VRPN change notifications fire
362void vrpn_DirectXRumblePad::mainloop() {
363 DIJOYSTATE2 js;
364
365 server_mainloop();
366
367 if (SendMessage(_wnd, MSG_POLL, 0, reinterpret_cast<LPARAM>(&js)) != POLL_SUCCESS) {
368 if ((time(NULL) - last_error) > 2) {
369 struct timeval now;
370
371 time(&last_error);
372 vrpn_gettimeofday(&now, NULL);
373 send_text_message("Cannot talk to joystick", now, vrpn_TEXT_ERROR);
374 }
375 return;
376 }
377
378 channel[0] = js.lX / 1000.0;
379 channel[1] = js.lY / 1000.0;
380 channel[2] = js.lZ / 1000.0;
381
382 channel[3] = js.lRx / 1000.0;
383 channel[4] = js.lRy / 1000.0;
384 channel[5] = js.lRz / 1000.0;
385
386 channel[6] = js.rglSlider[0] / 1000.0;
387 channel[7] = js.rglSlider[1] / 1000.0;
388
389 int i;
390 for (i = 0; i < 4; i++) {
391 long v = (long) js.rgdwPOV[i];
392 channel[8+i] = (v == -1) ? -1 : (v / 100.0);
393 }
394
395 for (i = 0; i < vrpn_Analog::num_channel/*min(128,vrpn_BUTTON_MAX_BUTTONS)*/; i++) {
396 buttons[i] = ( (js.rgbButtons[i] & 0x80) != 0);
397 }
398
399 // Send any changes out over the connection.
400 vrpn_gettimeofday(&_timestamp, NULL);
401 report_changes();
402}
403
404void vrpn_DirectXRumblePad::report(vrpn_uint32 class_of_service) {
405 vrpn_Analog::timestamp = _timestamp;
406 vrpn_Button::timestamp = _timestamp;
407
408 vrpn_Analog::report(class_of_service);
410}
411
412void vrpn_DirectXRumblePad::report_changes(vrpn_uint32 class_of_service) {
413 vrpn_Analog::timestamp = _timestamp;
414 vrpn_Button::timestamp = _timestamp;
415
416 vrpn_Analog::report_changes(class_of_service);
418}
419
420/* static */
421int vrpn_DirectXRumblePad::handle_request_message(void *userdata,
423{
424 const char* bufptr = p.buffer;
425 vrpn_int32 chan_num;
426 vrpn_int32 pad;
427 vrpn_float64 value;
428 vrpn_DirectXRumblePad* me = (vrpn_DirectXRumblePad*)userdata;
429
430 // Read the parameters from the buffer
431 vrpn_unbuffer(&bufptr, &chan_num);
432 vrpn_unbuffer(&bufptr, &pad);
433 vrpn_unbuffer(&bufptr, &value);
434
435 // Set the appropriate value, if the channel number is in the
436 // range of the ones we have.
437 if ( (chan_num < 0) || (chan_num >= me->o_num_channel) ) {
438 fprintf(stderr,"vrpn_Analog_Output_Server::handle_request_message(): Index out of bounds\n");
439 char msg[1024];
440 sprintf( msg, "Error: (handle_request_message): channel %d is not active. Squelching.", chan_num );
441 me->send_text_message( msg, p.msg_time, vrpn_TEXT_ERROR );
442 return 0;
443 }
444 me->o_channel[chan_num] = value;
445
446 float mag = static_cast<float>(value);
447 mag = (mag < 0) ? 0 : (mag > 1) ? 1 : mag;
448 SendMessage(me->_wnd, MSG_SETMAGNITUDE, *reinterpret_cast<WPARAM*>(&mag), 0);
449
450 return 0;
451}
452
453/* static */
454int vrpn_DirectXRumblePad::handle_request_channels_message(void* userdata,
456{
457 const char* bufptr = p.buffer;
458 vrpn_int32 num;
459 vrpn_int32 pad;
460 vrpn_DirectXRumblePad* me = (vrpn_DirectXRumblePad*)userdata;
461
462 // Read the values from the buffer
463 vrpn_unbuffer(&bufptr, &num);
464 vrpn_unbuffer(&bufptr, &pad);
465 if (num > me->o_num_channel)
466 {
467 char msg[1024];
468 sprintf( msg, "Error: (handle_request_channels_message): channels above %d not active; "
469 "bad request up to channel %d. Squelching.", me->o_num_channel, num );
470 me->send_text_message( msg, p.msg_time, vrpn_TEXT_ERROR );
471 num = me->o_num_channel;
472 }
473 if (num < 0)
474 {
475 char msg[1024];
476 sprintf( msg, "Error: (handle_request_channels_message): invalid channel %d. Squelching.", num );
477 me->send_text_message( msg, p.msg_time, vrpn_TEXT_ERROR );
478 return 0;
479 }
480
481 // Pull only channel 0 from the buffer, no matter how many values we received.
482 vrpn_float64 value;
483 vrpn_unbuffer(&bufptr, &value);
484 float mag = static_cast<float>(value);
485 mag = (mag < 0) ? 0 : (mag > 1) ? 1 : mag;
486 SendMessage(me->_wnd, MSG_SETMAGNITUDE, *reinterpret_cast<WPARAM*>(&mag), 0);
487
488 return 0;
489}
490
491int VRPN_CALLBACK vrpn_DirectXRumblePad::handle_last_connection_dropped(void *selfPtr, vrpn_HANDLERPARAM data) {
492 // Kill force feedback if no one is connected
493 SendMessage(((vrpn_DirectXRumblePad *)selfPtr)->_wnd, MSG_SETMAGNITUDE, 0, 0);
494 return 0;
495}
496
497// Initializes the joystick with a force feedback effect
498HRESULT vrpn_DirectXRumblePad::init_force() {
499 HRESULT hr;
500
501 // Turn off autocentering
502 DIPROPDWORD prop;
503 prop.diph.dwSize = sizeof(prop);
504 prop.diph.dwHeaderSize = sizeof(DIPROPHEADER);
505 prop.diph.dwObj = 0;
506 prop.diph.dwHow = DIPH_DEVICE;
507 prop.dwData = DIPROPAUTOCENTER_OFF;
508
509 hr = _gamepad->SetProperty(DIPROP_AUTOCENTER, &prop.diph);
510 if (FAILED(hr))
511 return hr;
512
513 // Create the actual effect
514 DWORD dwAxes[2] = {DIJOFS_X, DIJOFS_Y};
515 LONG lDirection[2] = {0, 0};
516
517 _diPeriodic.dwMagnitude = DI_FFNOMINALMAX;
518 _diPeriodic.lOffset = 0;
519 _diPeriodic.dwPhase = 0;
520 _diPeriodic.dwPeriod = static_cast<DWORD>(0.05 * DI_SECONDS);
521
522 _diEffect.dwSize = sizeof(DIEFFECT);
523 _diEffect.dwFlags = DIEFF_POLAR | DIEFF_OBJECTOFFSETS;
524 _diEffect.dwDuration = INFINITE;
525 _diEffect.dwSamplePeriod = 0;
526 _diEffect.dwGain = DI_FFNOMINALMAX;
527 _diEffect.dwTriggerButton = DIEB_NOTRIGGER;
528 _diEffect.dwTriggerRepeatInterval = 0;
529 _diEffect.cAxes = 2;
530 _diEffect.rgdwAxes = dwAxes;
531 _diEffect.rglDirection = &lDirection[0];
532 _diEffect.lpEnvelope = NULL;
533 _diEffect.cbTypeSpecificParams = sizeof(_diPeriodic);
534 _diEffect.lpvTypeSpecificParams = &_diPeriodic;
535 _diEffect.dwStartDelay = 0;
536
537 hr = _gamepad->CreateEffect(GUID_Square, &_diEffect, &_effect, NULL);
538
539 return hr;
540}
541
542
543#endif // _WIN32 and VRPN_USE_DIRECTINPUT
struct timeval timestamp
Definition: vrpn_Analog.h:41
vrpn_int32 num_channel
Definition: vrpn_Analog.h:40
virtual void report(vrpn_uint32 class_of_service=vrpn_CONNECTION_LOW_LATENCY, const struct timeval time=vrpn_ANALOG_NOW)
Send a report whether something has changed or not (for servers) Optionally, tell what time to stamp ...
Definition: vrpn_Analog.C:94
virtual void report_changes(vrpn_uint32 class_of_service=vrpn_CONNECTION_LOW_LATENCY, const struct timeval time=vrpn_ANALOG_NOW)
Send a report only if something has changed (for servers) Optionally, tell what time to stamp the val...
Definition: vrpn_Analog.C:71
All button servers should derive from this class, which provides the ability to turn any of the butto...
Definition: vrpn_Button.h:66
vrpn_int32 num_buttons
Definition: vrpn_Button.h:48
struct timeval timestamp
Definition: vrpn_Button.h:49
virtual void report_changes(void)
Definition: vrpn_Button.C:423
Generic connection class not specific to the transport mechanism.
This structure is what is passed to a vrpn_Connection message callback.
const char * buffer
struct timeval msg_time
@ vrpn_TEXT_ERROR
const int vrpn_BUTTON_MAX_BUTTONS
Definition: vrpn_Button.h:13
#define DIRECTINPUT_VERSION
#define VRPN_CALLBACK
const char * vrpn_dropped_last_connection
VRPN_API int vrpn_unbuffer(const char **buffer, timeval *t)
Utility routine for taking a struct timeval from a buffer that was sent as a message.
Definition: vrpn_Shared.C:321
#define vrpn_gettimeofday
Definition: vrpn_Shared.h:99
#define min(x, y)
Definition: vrpn_WiiMote.C:47