1/*
2 Simple DirectMedia Layer
3 Copyright (C) 2025 Simon Wood <simon@mungewell.org>
4 Copyright (C) 2025 Michal Malý <madcatxster@devoid-pointer.net>
5 Copyright (C) 2025 Bernat Arlandis <berarma@hotmail.com>
6 Copyright (C) 2025 Katharine Chui <katharine.chui@gmail.com>
7
8 This software is provided 'as-is', without any express or implied
9 warranty. In no event will the authors be held liable for any damages
10 arising from the use of this software.
11
12 Permission is granted to anyone to use this software for any purpose,
13 including commercial applications, and to alter it and redistribute it
14 freely, subject to the following restrictions:
15
16 1. The origin of this software must not be misrepresented; you must not
17 claim that you wrote the original software. If you use this software
18 in a product, an acknowledgment in the product documentation would be
19 appreciated but is not required.
20 2. Altered source versions must be plainly marked as such, and must not be
21 misrepresented as being the original software.
22 3. This notice may not be removed or altered from any source distribution.
23*/
24
25#include "SDL_internal.h"
26
27#ifdef SDL_JOYSTICK_HIDAPI
28
29#include "SDL_hidapihaptic_c.h"
30
31#ifdef SDL_HAPTIC_HIDAPI_LG4FF
32
33#include "SDL3/SDL_thread.h"
34#include "SDL3/SDL_mutex.h"
35#include "SDL3/SDL_timer.h"
36
37#include <math.h>
38
39#define USB_VENDOR_ID_LOGITECH 0x046d
40#define USB_DEVICE_ID_LOGITECH_G29_WHEEL 0xc24f
41#define USB_DEVICE_ID_LOGITECH_G27_WHEEL 0xc29b
42#define USB_DEVICE_ID_LOGITECH_G25_WHEEL 0xc299
43#define USB_DEVICE_ID_LOGITECH_DFGT_WHEEL 0xc29a
44#define USB_DEVICE_ID_LOGITECH_DFP_WHEEL 0xc298
45#define USB_DEVICE_ID_LOGITECH_WHEEL 0xc294
46
47static Uint32 supported_device_ids[] = {
48 USB_DEVICE_ID_LOGITECH_G29_WHEEL,
49 USB_DEVICE_ID_LOGITECH_G27_WHEEL,
50 USB_DEVICE_ID_LOGITECH_G25_WHEEL,
51 USB_DEVICE_ID_LOGITECH_DFGT_WHEEL,
52 USB_DEVICE_ID_LOGITECH_DFP_WHEEL,
53 USB_DEVICE_ID_LOGITECH_WHEEL
54};
55
56
57
58#define LG4FF_MAX_EFFECTS 16
59
60#define FF_EFFECT_STARTED 0
61#define FF_EFFECT_ALLSET 1
62#define FF_EFFECT_PLAYING 2
63#define FF_EFFECT_UPDATING 3
64
65struct lg4ff_effect_state {
66 SDL_HapticEffect effect;
67 Uint64 start_at;
68 Uint64 play_at;
69 Uint64 stop_at;
70 Uint32 flags;
71 Uint64 time_playing;
72 Uint64 updated_at;
73 Uint32 phase;
74 Uint32 phase_adj;
75 Uint32 count;
76
77 double direction_gain;
78 Sint32 slope;
79
80 bool allocated;
81};
82
83struct lg4ff_effect_parameters {
84 Sint32 level;
85 Sint32 d1;
86 Sint32 d2;
87 Sint32 k1;
88 Sint32 k2;
89 Uint32 clip;
90};
91
92struct lg4ff_slot {
93 Sint32 id;
94 struct lg4ff_effect_parameters parameters;
95 Uint8 current_cmd[7];
96 Uint32 cmd_op;
97 bool is_updated;
98 Uint32 effect_type;
99};
100
101typedef struct lg4ff_device {
102 Uint16 product_id;
103 Uint16 release_number;
104 struct lg4ff_effect_state states[LG4FF_MAX_EFFECTS];
105 struct lg4ff_slot slots[4];
106 Sint32 effects_used;
107
108 Sint32 gain;
109 Sint32 app_gain;
110
111 Sint32 spring_level;
112 Sint32 damper_level;
113 Sint32 friction_level;
114
115 Sint32 peak_ffb_level;
116
117 SDL_Joystick *hid_handle;
118
119 bool stop_thread;
120 SDL_Thread *thread;
121 char thread_name_buf[256];
122
123 SDL_Mutex *mutex;
124
125 bool is_ffex;
126} lg4ff_device;
127
128static SDL_INLINE Uint64 get_time_ms(void) {
129 return SDL_GetTicks();
130}
131
132#define test_bit(bit, field) (*(field) & (1 << bit))
133#define __set_bit(bit, field) {*(field) = *(field) | (1 << bit);}
134#define __clear_bit(bit, field) {*(field) = *(field) & ~(1 << bit);}
135#define sin_deg(in) (double)(SDL_sin((double)(in) * SDL_PI_D / 180.0))
136
137#define time_after_eq(a, b) (a >= b)
138#define time_before(a, b) (a < b)
139#define time_diff(a, b) (a - b)
140
141#define STOP_EFFECT(state) ((state)->flags = 0)
142
143#define CLAMP_VALUE_U16(x) ((Uint16)((x) > 0xffff ? 0xffff : (x)))
144#define SCALE_VALUE_U16(x, bits) (CLAMP_VALUE_U16(x) >> (16 - bits))
145#define CLAMP_VALUE_S16(x) ((Uint16)((x) <= -0x8000 ? -0x8000 : ((x) > 0x7fff ? 0x7fff : (x))))
146#define TRANSLATE_FORCE(x) ((CLAMP_VALUE_S16(x) + 0x8000) >> 8)
147#define SCALE_COEFF(x, bits) SCALE_VALUE_U16(abs32(x) * 2, bits)
148
149static SDL_INLINE Sint32 abs32(Sint32 x) {
150 return x < 0 ? -x : x;
151}
152static SDL_INLINE Sint64 abs64(Sint64 x) {
153 return x < 0 ? -x : x;
154}
155
156static SDL_INLINE bool effect_is_periodic(const SDL_HapticEffect *effect)
157{
158
159 return effect->type == SDL_HAPTIC_SINE ||
160 effect->type == SDL_HAPTIC_TRIANGLE ||
161 effect->type == SDL_HAPTIC_SAWTOOTHUP ||
162 effect->type == SDL_HAPTIC_SAWTOOTHDOWN ||
163 effect->type == SDL_HAPTIC_SQUARE;
164}
165
166static SDL_INLINE bool effect_is_condition(const SDL_HapticEffect *effect)
167{
168 return effect->type == SDL_HAPTIC_SPRING ||
169 effect->type == SDL_HAPTIC_DAMPER ||
170 effect->type == SDL_HAPTIC_FRICTION;
171}
172
173// linux SDL_syshaptic.c SDL_SYS_ToDirection
174static Uint16 to_linux_direction(SDL_HapticDirection *src)
175{
176 Uint32 tmp;
177
178 switch (src->type) {
179 case SDL_HAPTIC_POLAR:
180 tmp = ((src->dir[0] % 36000) * 0x8000) / 18000; /* convert to range [0,0xFFFF] */
181 return (Uint16)tmp;
182
183 case SDL_HAPTIC_SPHERICAL:
184 /*
185 We convert to polar, because that's the only supported direction on Linux.
186 The first value of a spherical direction is practically the same as a
187 Polar direction, except that we have to add 90 degrees. It is the angle
188 from EAST {1,0} towards SOUTH {0,1}.
189 --> add 9000
190 --> finally convert to [0,0xFFFF] as in case SDL_HAPTIC_POLAR.
191 */
192 tmp = ((src->dir[0]) + 9000) % 36000; /* Convert to polars */
193 tmp = (tmp * 0x8000) / 18000; /* convert to range [0,0xFFFF] */
194 return (Uint16)tmp;
195
196 case SDL_HAPTIC_CARTESIAN:
197 if (!src->dir[1]) {
198 return (Uint16) (src->dir[0] >= 0 ? 0x4000 : 0xC000);
199 } else if (!src->dir[0]) {
200 return (Uint16) (src->dir[1] >= 0 ? 0x8000 : 0);
201 } else {
202 float f = (float)SDL_atan2(src->dir[1], src->dir[0]); /* Ideally we'd use fixed point math instead of floats... */
203 /*
204 SDL_atan2 takes the parameters: Y-axis-value and X-axis-value (in that order)
205 - Y-axis-value is the second coordinate (from center to SOUTH)
206 - X-axis-value is the first coordinate (from center to EAST)
207 We add 36000, because SDL_atan2 also returns negative values. Then we practically
208 have the first spherical value. Therefore we proceed as in case
209 SDL_HAPTIC_SPHERICAL and add another 9000 to get the polar value.
210 --> add 45000 in total
211 --> finally convert to [0,0xFFFF] as in case SDL_HAPTIC_POLAR.
212 */
213 tmp = (((Sint32) (f * 18000. / SDL_PI_D)) + 45000) % 36000;
214 tmp = (tmp * 0x8000) / 18000; /* convert to range [0,0xFFFF] */
215 return (Uint16)tmp;
216 }
217 break;
218 case SDL_HAPTIC_STEERING_AXIS:
219 return 0x4000;
220 default:
221 SDL_assert(0);
222 }
223
224 return 0;
225}
226
227static Uint16 get_effect_direction(SDL_HapticEffect *effect)
228{
229 Uint16 direction = 0;
230 if (effect_is_periodic(effect)) {
231 direction = to_linux_direction(&effect->periodic.direction);
232 } else if (effect_is_condition(effect)) {
233 direction = to_linux_direction(&effect->condition.direction);
234 } else {
235 switch(effect->type) {
236 case SDL_HAPTIC_CONSTANT:
237 direction = to_linux_direction(&effect->constant.direction);
238 break;
239 case SDL_HAPTIC_RAMP:
240 direction = to_linux_direction(&effect->ramp.direction);
241 break;
242 default:
243 SDL_assert(0);
244 }
245 }
246
247 return direction;
248}
249
250static Uint32 get_effect_replay_length(SDL_HapticEffect *effect)
251{
252 Uint32 length = 0;
253 if (effect_is_periodic(effect)) {
254 length = effect->periodic.length;
255 } else if (effect_is_condition(effect)) {
256 length = effect->condition.length;
257 } else {
258 switch(effect->type) {
259 case SDL_HAPTIC_CONSTANT:
260 length = effect->constant.length;
261 break;
262 case SDL_HAPTIC_RAMP:
263 length = effect->ramp.length;
264 break;
265 default:
266 SDL_assert(0);
267 }
268 }
269
270 if (length == SDL_HAPTIC_INFINITY) {
271 length = 0;
272 }
273
274 return length;
275}
276
277static Uint16 get_effect_replay_delay(SDL_HapticEffect *effect)
278{
279 Uint16 delay = 0;
280 if (effect_is_periodic(effect)) {
281 delay = effect->periodic.delay;
282 } else if (effect_is_condition(effect)) {
283 delay = effect->condition.delay;
284 } else {
285 switch(effect->type) {
286 case SDL_HAPTIC_CONSTANT:
287 delay = effect->constant.delay;
288 break;
289 case SDL_HAPTIC_RAMP:
290 delay = effect->ramp.delay;
291 break;
292 default:
293 SDL_assert(0);
294 }
295 }
296
297 return delay;
298}
299
300/*
301 *Ported*
302 Original function by:
303 Bernat Arlandis <berarma@hotmail.com>
304 `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git
305*/
306static int lg4ff_play_effect(struct lg4ff_device *device, int effect_id, int value)
307{
308 struct lg4ff_effect_state *state;
309 Uint64 now = get_time_ms();
310
311 state = &device->states[effect_id];
312
313 if (value > 0) {
314 if (test_bit(FF_EFFECT_STARTED, &state->flags)) {
315 STOP_EFFECT(state);
316 } else {
317 device->effects_used++;
318 }
319 __set_bit(FF_EFFECT_STARTED, &state->flags);
320 state->start_at = now;
321 state->count = value;
322 } else {
323 if (test_bit(FF_EFFECT_STARTED, &state->flags)) {
324 STOP_EFFECT(state);
325 device->effects_used--;
326 }
327 }
328
329 return 0;
330}
331
332/*
333 *Ported*
334 Original function by:
335 Bernat Arlandis <berarma@hotmail.com>
336 `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git
337*/
338static int lg4ff_upload_effect(struct lg4ff_device *device, const SDL_HapticEffect *effect, int id)
339{
340 struct lg4ff_effect_state *state;
341 Uint64 now = get_time_ms();
342
343 if (effect_is_periodic(effect) && effect->periodic.period == 0) {
344 return -1;
345 }
346
347 state = &device->states[id];
348
349 if (test_bit(FF_EFFECT_STARTED, &state->flags) && effect->type != state->effect.type) {
350 return -1;
351 }
352
353 state->effect = *effect;
354
355 if (test_bit(FF_EFFECT_STARTED, &state->flags)) {
356 __set_bit(FF_EFFECT_UPDATING, &state->flags);
357 state->updated_at = now;
358 }
359
360 return 0;
361}
362
363/*
364 *Ported*
365 Original function by:
366 Bernat Arlandis <berarma@hotmail.com>
367 `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git
368*/
369static void lg4ff_update_state(struct lg4ff_effect_state *state, const Uint64 now)
370{
371 SDL_HapticEffect *effect = &state->effect;
372 Uint64 phase_time;
373 Uint16 effect_direction = get_effect_direction(effect);
374
375 if (!test_bit(FF_EFFECT_ALLSET, &state->flags)) {
376 state->play_at = state->start_at + get_effect_replay_delay(effect);
377 if (!test_bit(FF_EFFECT_UPDATING, &state->flags)) {
378 state->updated_at = state->play_at;
379 }
380 state->direction_gain = sin_deg(effect_direction * 360 / 0x10000);
381 if (effect_is_periodic(effect)) {
382 state->phase_adj = effect->periodic.phase * 360 / effect->periodic.period;
383 }
384 if (get_effect_replay_length(effect)) {
385 state->stop_at = state->play_at + get_effect_replay_length(effect);
386 }
387 }
388 __set_bit(FF_EFFECT_ALLSET, &state->flags);
389
390 if (test_bit(FF_EFFECT_UPDATING, &state->flags)) {
391 __clear_bit(FF_EFFECT_PLAYING, &state->flags);
392 state->play_at = state->updated_at + get_effect_replay_delay(effect);
393 state->direction_gain = sin_deg(effect_direction * 360 / 0x10000);
394 if (get_effect_replay_length(effect)) {
395 state->stop_at = state->updated_at + get_effect_replay_length(effect);
396 }
397 if (effect_is_periodic(effect)) {
398 state->phase_adj = state->phase;
399 }
400 }
401 __clear_bit(FF_EFFECT_UPDATING, &state->flags);
402
403 state->slope = 0;
404 if (effect->type == SDL_HAPTIC_RAMP && effect->ramp.length && (effect->ramp.length - effect->ramp.attack_length - effect->ramp.fade_length) != 0) {
405 state->slope = ((effect->ramp.end - effect->ramp.start) << 16) / (effect->ramp.length - effect->ramp.attack_length - effect->ramp.fade_length);
406 }
407
408 if (!test_bit(FF_EFFECT_PLAYING, &state->flags) && time_after_eq(now,
409 state->play_at) && (get_effect_replay_length(effect) == 0 ||
410 time_before(now, state->stop_at))) {
411 __set_bit(FF_EFFECT_PLAYING, &state->flags);
412 }
413
414 if (test_bit(FF_EFFECT_PLAYING, &state->flags)) {
415 state->time_playing = time_diff(now, state->play_at);
416 if (effect_is_periodic(effect)) {
417 phase_time = time_diff(now, state->updated_at);
418 state->phase = (phase_time % effect->periodic.period) * 360 / effect->periodic.period;
419 state->phase += state->phase_adj % 360;
420 }
421 }
422}
423
424/*
425 *Ported*
426 Original function by:
427 Bernat Arlandis <berarma@hotmail.com>
428 `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git
429*/
430static Sint32 lg4ff_calculate_constant(struct lg4ff_effect_state *state)
431{
432 SDL_HapticConstant *constant = (SDL_HapticConstant *)&state->effect;
433 Sint32 level_sign;
434 Sint32 level = constant->level;
435 Sint32 d, t;
436
437 if (state->time_playing < constant->attack_length) {
438 level_sign = level < 0 ? -1 : 1;
439 d = level - level_sign * constant->attack_level;
440 level = (Sint32) (level_sign * constant->attack_level + d * state->time_playing / constant->attack_length);
441 } else if (constant->length && constant->fade_length) {
442 t = (Sint32) (state->time_playing - constant->length + constant->fade_length);
443 if (t > 0) {
444 level_sign = level < 0 ? -1 : 1;
445 d = level - level_sign * constant->fade_level;
446 level = level - d * t / constant->fade_length;
447 }
448 }
449
450 return (Sint32)(state->direction_gain * level);
451}
452
453/*
454 *Ported*
455 Original function by:
456 Bernat Arlandis <berarma@hotmail.com>
457 `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git
458*/
459static Sint32 lg4ff_calculate_ramp(struct lg4ff_effect_state *state)
460{
461 SDL_HapticRamp *ramp = (SDL_HapticRamp *)&state->effect;
462 Sint32 level_sign;
463 Sint32 level;
464 Sint32 d, t;
465
466 if (state->time_playing < ramp->attack_length) {
467 level = ramp->start;
468 level_sign = level < 0 ? -1 : 1;
469 t = (Sint32) (ramp->attack_length - state->time_playing);
470 d = level - level_sign * ramp->attack_level;
471 level = level_sign * ramp->attack_level + d * t / ramp->attack_length;
472 } else if (ramp->length && state->time_playing >= ramp->length - ramp->fade_length && ramp->fade_length) {
473 level = ramp->end;
474 level_sign = level < 0 ? -1 : 1;
475 t = (Sint32) (state->time_playing - ramp->length + ramp->fade_length);
476 d = level_sign * ramp->fade_level - level;
477 level = level - d * t / ramp->fade_length;
478 } else {
479 t = (Sint32) (state->time_playing - ramp->attack_length);
480 level = ramp->start + ((t * state->slope) >> 16);
481 }
482
483 return (Sint32)(state->direction_gain * level);
484}
485
486/*
487 *Ported*
488 Original function by:
489 Bernat Arlandis <berarma@hotmail.com>
490 `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git
491*/
492static Sint32 lg4ff_calculate_periodic(struct lg4ff_effect_state *state)
493{
494 SDL_HapticPeriodic *periodic = (SDL_HapticPeriodic *)&state->effect;
495 Sint32 magnitude = periodic->magnitude;
496 Sint32 magnitude_sign = magnitude < 0 ? -1 : 1;
497 Sint32 level = periodic->offset;
498 Sint32 d, t;
499
500 if (state->time_playing < periodic->attack_length) {
501 d = magnitude - magnitude_sign * periodic->attack_level;
502 magnitude = (Sint32) (magnitude_sign * periodic->attack_level + d * state->time_playing / periodic->attack_length);
503 } else if (periodic->length && periodic->fade_length) {
504 t = (Sint32) (state->time_playing - get_effect_replay_length(&state->effect) + periodic->fade_length);
505 if (t > 0) {
506 d = magnitude - magnitude_sign * periodic->fade_level;
507 magnitude = magnitude - d * t / periodic->fade_length;
508 }
509 }
510
511 switch (periodic->type) {
512 case SDL_HAPTIC_SINE:
513 level += (Sint32)(sin_deg(state->phase) * magnitude);
514 break;
515 case SDL_HAPTIC_SQUARE:
516 level += (state->phase < 180 ? 1 : -1) * magnitude;
517 break;
518 case SDL_HAPTIC_TRIANGLE:
519 level += (Sint32) (abs64((Sint64)state->phase * magnitude * 2 / 360 - magnitude) * 2 - magnitude);
520 break;
521 case SDL_HAPTIC_SAWTOOTHUP:
522 level += state->phase * magnitude * 2 / 360 - magnitude;
523 break;
524 case SDL_HAPTIC_SAWTOOTHDOWN:
525 level += magnitude - state->phase * magnitude * 2 / 360;
526 break;
527 default:
528 SDL_assert(0);
529 }
530
531 return (Sint32)(state->direction_gain * level);
532}
533
534/*
535 *Ported*
536 Original function by:
537 Bernat Arlandis <berarma@hotmail.com>
538 `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git
539*/
540static void lg4ff_calculate_spring(struct lg4ff_effect_state *state, struct lg4ff_effect_parameters *parameters)
541{
542 SDL_HapticCondition *condition = (SDL_HapticCondition *)&state->effect;
543
544 parameters->d1 = ((Sint32)condition->center[0]) - condition->deadband[0] / 2;
545 parameters->d2 = ((Sint32)condition->center[0]) + condition->deadband[0] / 2;
546 parameters->k1 = condition->left_coeff[0];
547 parameters->k2 = condition->right_coeff[0];
548 parameters->clip = (Uint16)condition->right_sat[0];
549}
550
551/*
552 *Ported*
553 Original function by:
554 Bernat Arlandis <berarma@hotmail.com>
555 `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git
556*/
557static void lg4ff_calculate_resistance(struct lg4ff_effect_state *state, struct lg4ff_effect_parameters *parameters)
558{
559 SDL_HapticCondition *condition = (SDL_HapticCondition *)&state->effect;
560
561 parameters->k1 = condition->left_coeff[0];
562 parameters->k2 = condition->right_coeff[0];
563 parameters->clip = (Uint16)condition->right_sat[0];
564}
565
566/*
567 *Ported*
568 Original function by:
569 Bernat Arlandis <berarma@hotmail.com>
570 `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git
571*/
572static void lg4ff_update_slot(struct lg4ff_slot *slot, struct lg4ff_effect_parameters *parameters)
573{
574 Uint8 original_cmd[7];
575 Sint32 d1;
576 Sint32 d2;
577 Sint32 k1;
578 Sint32 k2;
579 Sint32 s1;
580 Sint32 s2;
581
582 SDL_memcpy(original_cmd, slot->current_cmd, sizeof(original_cmd));
583
584 if ((original_cmd[0] & 0xf) == 1) {
585 original_cmd[0] = (original_cmd[0] & 0xf0) + 0xc;
586 }
587
588 if (slot->effect_type == SDL_HAPTIC_CONSTANT) {
589 if (slot->cmd_op == 0) {
590 slot->cmd_op = 1;
591 } else {
592 slot->cmd_op = 0xc;
593 }
594 } else {
595 if (parameters->clip == 0) {
596 slot->cmd_op = 3;
597 } else if (slot->cmd_op == 3) {
598 slot->cmd_op = 1;
599 } else {
600 slot->cmd_op = 0xc;
601 }
602 }
603
604 slot->current_cmd[0] = (Uint8)((0x10 << slot->id) + slot->cmd_op);
605
606 if (slot->cmd_op == 3) {
607 slot->current_cmd[1] = 0;
608 slot->current_cmd[2] = 0;
609 slot->current_cmd[3] = 0;
610 slot->current_cmd[4] = 0;
611 slot->current_cmd[5] = 0;
612 slot->current_cmd[6] = 0;
613 } else {
614 switch (slot->effect_type) {
615 case SDL_HAPTIC_CONSTANT:
616 slot->current_cmd[1] = 0x00;
617 slot->current_cmd[2] = 0;
618 slot->current_cmd[3] = 0;
619 slot->current_cmd[4] = 0;
620 slot->current_cmd[5] = 0;
621 slot->current_cmd[6] = 0;
622 slot->current_cmd[2 + slot->id] = TRANSLATE_FORCE(parameters->level);
623 break;
624 case SDL_HAPTIC_SPRING:
625 d1 = SCALE_VALUE_U16(((parameters->d1) + 0x8000) & 0xffff, 11);
626 d2 = SCALE_VALUE_U16(((parameters->d2) + 0x8000) & 0xffff, 11);
627 s1 = parameters->k1 < 0;
628 s2 = parameters->k2 < 0;
629 k1 = abs32(parameters->k1);
630 k2 = abs32(parameters->k2);
631 if (k1 < 2048) {
632 d1 = 0;
633 } else {
634 k1 -= 2048;
635 }
636 if (k2 < 2048) {
637 d2 = 2047;
638 } else {
639 k2 -= 2048;
640 }
641 slot->current_cmd[1] = 0x0b;
642 slot->current_cmd[2] = (Uint8)(d1 >> 3);
643 slot->current_cmd[3] = (Uint8)(d2 >> 3);
644 slot->current_cmd[4] = (SCALE_COEFF(k2, 4) << 4) + SCALE_COEFF(k1, 4);
645 slot->current_cmd[5] = (Uint8)(((d2 & 7) << 5) + ((d1 & 7) << 1) + (s2 << 4) + s1);
646 slot->current_cmd[6] = SCALE_VALUE_U16(parameters->clip, 8);
647 break;
648 case SDL_HAPTIC_DAMPER:
649 s1 = parameters->k1 < 0;
650 s2 = parameters->k2 < 0;
651 slot->current_cmd[1] = 0x0c;
652 slot->current_cmd[2] = SCALE_COEFF(parameters->k1, 4);
653 slot->current_cmd[3] = (Uint8)s1;
654 slot->current_cmd[4] = SCALE_COEFF(parameters->k2, 4);
655 slot->current_cmd[5] = (Uint8)s2;
656 slot->current_cmd[6] = SCALE_VALUE_U16(parameters->clip, 8);
657 break;
658 case SDL_HAPTIC_FRICTION:
659 s1 = parameters->k1 < 0;
660 s2 = parameters->k2 < 0;
661 slot->current_cmd[1] = 0x0e;
662 slot->current_cmd[2] = SCALE_COEFF(parameters->k1, 8);
663 slot->current_cmd[3] = SCALE_COEFF(parameters->k2, 8);
664 slot->current_cmd[4] = SCALE_VALUE_U16(parameters->clip, 8);
665 slot->current_cmd[5] = (Uint8)((s2 << 4) + s1);
666 slot->current_cmd[6] = 0;
667 break;
668 }
669 }
670
671 if (SDL_memcmp(original_cmd, slot->current_cmd, sizeof(original_cmd))) {
672 slot->is_updated = 1;
673 }
674}
675
676/*
677 *Ported*
678 Original function by:
679 Bernat Arlandis <berarma@hotmail.com>
680 `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git
681*/
682static int lg4ff_init_slots(struct lg4ff_device *device)
683{
684 struct lg4ff_effect_parameters parameters;
685 Uint8 cmd[7] = {0};
686 int i;
687 bool ret;
688
689 // Set/unset fixed loop mode
690 cmd[0] = 0x0d;
691 //cmd[1] = fixed_loop ? 1 : 0;
692 cmd[1] = 0;
693 ret = SDL_SendJoystickEffect(device->hid_handle, cmd, 7);
694 if (!ret) {
695 return -1;
696 }
697
698 SDL_memset(&device->states, 0, sizeof(device->states));
699 SDL_memset(&device->slots, 0, sizeof(device->slots));
700 SDL_memset(&parameters, 0, sizeof(parameters));
701
702 device->slots[0].effect_type = SDL_HAPTIC_CONSTANT;
703 device->slots[1].effect_type = SDL_HAPTIC_SPRING;
704 device->slots[2].effect_type = SDL_HAPTIC_DAMPER;
705 device->slots[3].effect_type = SDL_HAPTIC_FRICTION;
706
707 for (i = 0; i < 4; i++) {
708 device->slots[i].id = i;
709 lg4ff_update_slot(&device->slots[i], &parameters);
710 ret = SDL_SendJoystickEffect(device->hid_handle, cmd, 7);
711 if (!ret) {
712 return -1;
713 }
714 device->slots[i].is_updated = 0;
715 }
716
717 return 0;
718}
719
720/*
721 *Ported*
722 Original function by:
723 Bernat Arlandis <berarma@hotmail.com>
724 `git blame 1a2d5727876dd7befce23d9695924e9446b31c4b hid-lg4ff.c`, https://github.com/berarma/new-lg4ff.git
725*/
726static int lg4ff_timer(struct lg4ff_device *device)
727{
728 struct lg4ff_slot *slot;
729 struct lg4ff_effect_state *state;
730 struct lg4ff_effect_parameters parameters[4];
731 Uint64 now = get_time_ms();
732 Uint16 gain;
733 Sint32 count;
734 Sint32 effect_id;
735 int i;
736 Sint32 ffb_level;
737 int status = 0;
738
739 // XXX how to detect stacked up effects here?
740
741 SDL_memset(parameters, 0, sizeof(parameters));
742
743 gain = (Uint16)((Uint32)device->gain * device->app_gain / 0xffff);
744
745 count = device->effects_used;
746
747 for (effect_id = 0; effect_id < LG4FF_MAX_EFFECTS; effect_id++) {
748
749 if (!count) {
750 break;
751 }
752
753 state = &device->states[effect_id];
754
755 if (!test_bit(FF_EFFECT_STARTED, &state->flags)) {
756 continue;
757 }
758
759 count--;
760
761 if (test_bit(FF_EFFECT_ALLSET, &state->flags)) {
762 if (get_effect_replay_length(&state->effect) && time_after_eq(now, state->stop_at)) {
763 STOP_EFFECT(state);
764 if (!--state->count) {
765 device->effects_used--;
766 continue;
767 }
768 __set_bit(FF_EFFECT_STARTED, &state->flags);
769 state->start_at = state->stop_at;
770 }
771 }
772
773 lg4ff_update_state(state, now);
774
775 if (!test_bit(FF_EFFECT_PLAYING, &state->flags)) {
776 continue;
777 }
778
779 if (effect_is_periodic(&state->effect)) {
780 parameters[0].level += lg4ff_calculate_periodic(state);
781 } else {
782 switch (state->effect.type) {
783 case SDL_HAPTIC_CONSTANT:
784 parameters[0].level += lg4ff_calculate_constant(state);
785 break;
786 case SDL_HAPTIC_RAMP:
787 parameters[0].level += lg4ff_calculate_ramp(state);
788 break;
789 case SDL_HAPTIC_SPRING:
790 lg4ff_calculate_spring(state, &parameters[1]);
791 break;
792 case SDL_HAPTIC_DAMPER:
793 lg4ff_calculate_resistance(state, &parameters[2]);
794 break;
795 case SDL_HAPTIC_FRICTION:
796 lg4ff_calculate_resistance(state, &parameters[3]);
797 break;
798 }
799 }
800 }
801
802 parameters[0].level = (Sint64)parameters[0].level * gain / 0xffff;
803 parameters[1].clip = parameters[1].clip * device->spring_level / 100;
804 parameters[2].clip = parameters[2].clip * device->damper_level / 100;
805 parameters[3].clip = parameters[3].clip * device->friction_level / 100;
806
807 ffb_level = abs32(parameters[0].level);
808 for (i = 1; i < 4; i++) {
809 parameters[i].k1 = (Sint64)parameters[i].k1 * gain / 0xffff;
810 parameters[i].k2 = (Sint64)parameters[i].k2 * gain / 0xffff;
811 parameters[i].clip = parameters[i].clip * gain / 0xffff;
812 ffb_level = (Sint32)(ffb_level + parameters[i].clip * 0x7fff / 0xffff);
813 }
814 if (ffb_level > device->peak_ffb_level) {
815 device->peak_ffb_level = ffb_level;
816 }
817
818 for (i = 0; i < 4; i++) {
819 slot = &device->slots[i];
820 lg4ff_update_slot(slot, &parameters[i]);
821 if (slot->is_updated) {
822 bool ret = SDL_SendJoystickEffect(device->hid_handle, slot->current_cmd, 7);
823 if (!ret) {
824 status = -1;
825 }
826 slot->is_updated = 0;
827 }
828 }
829
830 return status;
831}
832
833static bool SDL_HIDAPI_HapticDriverLg4ff_JoystickSupported(SDL_Joystick *joystick)
834{
835 Uint16 vendor_id = SDL_GetJoystickVendor(joystick);
836 Uint16 product_id = SDL_GetJoystickProduct(joystick);
837 if (vendor_id != USB_VENDOR_ID_LOGITECH) {
838 return false;
839 }
840 for (int i = 0;i < sizeof(supported_device_ids) / sizeof(Uint32);i++) {
841 if (supported_device_ids[i] == product_id) {
842 return true;
843 }
844 }
845 return false;
846}
847
848static int SDLCALL SDL_HIDAPI_HapticDriverLg4ff_ThreadFunction(void *ctx_in)
849{
850 lg4ff_device *ctx = (lg4ff_device *)ctx_in;
851 while (true) {
852 if (ctx->stop_thread) {
853 return 0;
854 }
855 SDL_LockMutex(ctx->mutex);
856 lg4ff_timer(ctx);
857 SDL_UnlockMutex(ctx->mutex);
858 SDL_Delay(2);
859 }
860}
861
862static int SDL_HIDAPI_HapticDriverLg4ff_GetEnvInt(const char *env_name, int min, int max, int def)
863{
864 const char *env = SDL_getenv(env_name);
865 int value = 0;
866 if (env == NULL) {
867 return def;
868 }
869 value = SDL_atoi(env);
870 if (value < min) {
871 value = min;
872 }
873 if (value > max) {
874 value = max;
875 }
876 return value;
877}
878
879/*
880 ffex identification method by:
881 Simon Wood <simon@mungewell.org>
882 Michal Malý <madcatxster@devoid-pointer.net> <madcatxster@gmail.com>
883 lg4ff_init
884 `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git
885*/
886static void *SDL_HIDAPI_HapticDriverLg4ff_Open(SDL_Joystick *joystick)
887{
888 lg4ff_device *ctx;
889 if (!SDL_HIDAPI_HapticDriverLg4ff_JoystickSupported(joystick)) {
890 SDL_SetError("Device not supported by the lg4ff hidapi haptic driver");
891 return NULL;
892 }
893
894 ctx = SDL_malloc(sizeof(lg4ff_device));
895 if (ctx == NULL) {
896 SDL_OutOfMemory();
897 return NULL;
898 }
899 SDL_memset(ctx, 0, sizeof(lg4ff_device));
900
901 ctx->hid_handle = joystick;
902 if (lg4ff_init_slots(ctx) != 0) {
903 SDL_SetError("lg4ff hidapi driver failed initializing effect slots");
904 SDL_free(ctx);
905 return NULL;
906 }
907
908 ctx->mutex = SDL_CreateMutex();
909 if (ctx->mutex == NULL) {
910 SDL_free(ctx);
911 return NULL;
912 }
913
914 ctx->spring_level = SDL_HIDAPI_HapticDriverLg4ff_GetEnvInt("SDL_HAPTIC_LG4FF_SPRING", 0, 100, 30);
915 ctx->damper_level = SDL_HIDAPI_HapticDriverLg4ff_GetEnvInt("SDL_HAPTIC_LG4FF_DAMPER", 0, 100, 30);
916 ctx->friction_level = SDL_HIDAPI_HapticDriverLg4ff_GetEnvInt("SDL_HAPTIC_LG4FF_FRICTION", 0, 100, 30);
917 ctx->gain = SDL_HIDAPI_HapticDriverLg4ff_GetEnvInt("SDL_HAPTIC_LG4FF_GAIN", 0, 65535, 65535);
918 ctx->app_gain = 65535;
919
920 ctx->product_id = SDL_GetJoystickProduct(joystick);
921 ctx->release_number = SDL_GetJoystickProductVersion(joystick);
922
923 SDL_snprintf(ctx->thread_name_buf, sizeof(ctx->thread_name_buf), "SDL_hidapihaptic_lg4ff %d %04x:%04x", SDL_GetJoystickID(joystick), USB_VENDOR_ID_LOGITECH, ctx->product_id);
924 ctx->stop_thread = false;
925 ctx->thread = SDL_CreateThread(SDL_HIDAPI_HapticDriverLg4ff_ThreadFunction, ctx->thread_name_buf, ctx);
926
927 if (ctx->product_id == USB_DEVICE_ID_LOGITECH_WHEEL &&
928 (ctx->release_number >> 8) == 0x21 &&
929 (ctx->release_number & 0xff) == 0x00) {
930 ctx->is_ffex = true;
931 } else {
932 ctx->is_ffex = false;
933 }
934
935 return ctx;
936}
937
938static bool SDL_HIDAPI_HapticDriverLg4ff_StopEffects(SDL_HIDAPI_HapticDevice *device)
939{
940 lg4ff_device *ctx = (lg4ff_device *)device->ctx;
941 int i;
942
943 SDL_LockMutex(ctx->mutex);
944 for (i = 0;i < LG4FF_MAX_EFFECTS;i++) {
945 struct lg4ff_effect_state *state = &ctx->states[i];
946 STOP_EFFECT(state);
947 }
948 SDL_UnlockMutex(ctx->mutex);
949
950 return true;
951}
952
953static void SDL_HIDAPI_HapticDriverLg4ff_Close(SDL_HIDAPI_HapticDevice *device)
954{
955 lg4ff_device *ctx = (lg4ff_device *)device->ctx;
956
957 SDL_HIDAPI_HapticDriverLg4ff_StopEffects(device);
958
959 // let effects finish in lg4ff_timer
960 SDL_Delay(50);
961
962 ctx->stop_thread = true;
963 SDL_WaitThread(ctx->thread, NULL);
964 SDL_DestroyMutex(ctx->mutex);
965}
966
967static int SDL_HIDAPI_HapticDriverLg4ff_NumEffects(SDL_HIDAPI_HapticDevice *device)
968{
969 return LG4FF_MAX_EFFECTS;
970}
971
972static Uint32 SDL_HIDAPI_HapticDriverLg4ff_GetFeatures(SDL_HIDAPI_HapticDevice *device)
973{
974 return SDL_HAPTIC_CONSTANT |
975 SDL_HAPTIC_SPRING |
976 SDL_HAPTIC_DAMPER |
977 SDL_HAPTIC_AUTOCENTER |
978 SDL_HAPTIC_SINE |
979 SDL_HAPTIC_SQUARE |
980 SDL_HAPTIC_TRIANGLE |
981 SDL_HAPTIC_SAWTOOTHUP |
982 SDL_HAPTIC_SAWTOOTHDOWN |
983 SDL_HAPTIC_RAMP |
984 SDL_HAPTIC_FRICTION |
985 SDL_HAPTIC_STATUS |
986 SDL_HAPTIC_GAIN;
987}
988
989static bool SDL_HIDAPI_HapticDriverLg4ff_EffectSupported(SDL_HIDAPI_HapticDevice *device, const SDL_HapticEffect *effect) {
990 Uint32 features = SDL_HIDAPI_HapticDriverLg4ff_GetFeatures(device);
991 return (features & effect->type)? true : false;
992}
993
994static int SDL_HIDAPI_HapticDriverLg4ff_NumAxes(SDL_HIDAPI_HapticDevice *device)
995{
996 return 1;
997}
998
999static int SDL_HIDAPI_HapticDriverLg4ff_CreateEffect(SDL_HIDAPI_HapticDevice *device, const SDL_HapticEffect *data)
1000{
1001 lg4ff_device *ctx = (lg4ff_device *)device->ctx;
1002 int i;
1003 int state_slot = -1;
1004 int ret;
1005 if (!SDL_HIDAPI_HapticDriverLg4ff_EffectSupported(device, data)) {
1006 SDL_SetError("Unsupported effect");
1007 return -1;
1008 }
1009
1010 SDL_LockMutex(ctx->mutex);
1011 for (i = 0;i < LG4FF_MAX_EFFECTS;i++) {
1012 if (!ctx->states[i].allocated) {
1013 state_slot = i;
1014 break;
1015 }
1016 }
1017 if (state_slot == -1) {
1018 SDL_UnlockMutex(ctx->mutex);
1019 SDL_SetError("All effect slots in-use");
1020 return -1;
1021 }
1022
1023 ret = lg4ff_upload_effect(ctx, data, state_slot);
1024 SDL_UnlockMutex(ctx->mutex);
1025 if (ret == 0) {
1026 ctx->states[state_slot].allocated = true;
1027 return state_slot;
1028 } else {
1029 SDL_SetError("Bad effect parameters");
1030 return -1;
1031 }
1032}
1033
1034// assumes ctx->mutex locked
1035static bool lg4ff_effect_slot_valid_active(lg4ff_device *ctx, int id)
1036{
1037 if (id >= LG4FF_MAX_EFFECTS || id < 0) {
1038 return false;
1039 }
1040 if (!ctx->states[id].allocated) {
1041 return false;
1042 }
1043 return true;
1044}
1045
1046static bool SDL_HIDAPI_HapticDriverLg4ff_UpdateEffect(SDL_HIDAPI_HapticDevice *device, int id, const SDL_HapticEffect *data)
1047{
1048 lg4ff_device *ctx = (lg4ff_device *)device->ctx;
1049 int ret;
1050
1051 SDL_LockMutex(ctx->mutex);
1052 if (!lg4ff_effect_slot_valid_active(ctx, id)) {
1053 SDL_UnlockMutex(ctx->mutex);
1054 SDL_SetError("Bad effect id");
1055 return false;
1056 }
1057
1058 ret = lg4ff_upload_effect(ctx, data, id);
1059 SDL_UnlockMutex(ctx->mutex);
1060
1061 return ret == 0;
1062}
1063
1064static bool SDL_HIDAPI_HapticDriverLg4ff_RunEffect(SDL_HIDAPI_HapticDevice *device, int id, Uint32 iterations)
1065{
1066 lg4ff_device *ctx = (lg4ff_device *)device->ctx;
1067 int ret;
1068
1069 SDL_LockMutex(ctx->mutex);
1070 if (!lg4ff_effect_slot_valid_active(ctx, id)) {
1071 SDL_UnlockMutex(ctx->mutex);
1072 SDL_SetError("Bad effect id");
1073 return false;
1074 }
1075
1076 ret = lg4ff_play_effect(ctx, id, iterations);
1077 SDL_UnlockMutex(ctx->mutex);
1078
1079 return ret == 0;
1080}
1081
1082static bool SDL_HIDAPI_HapticDriverLg4ff_StopEffect(SDL_HIDAPI_HapticDevice *device, int id)
1083{
1084 return SDL_HIDAPI_HapticDriverLg4ff_RunEffect(device, id, 0);
1085}
1086
1087static void SDL_HIDAPI_HapticDriverLg4ff_DestroyEffect(SDL_HIDAPI_HapticDevice *device, int id)
1088{
1089 lg4ff_device *ctx = (lg4ff_device *)device->ctx;
1090 struct lg4ff_effect_state *state;
1091
1092 SDL_LockMutex(ctx->mutex);
1093 if (!lg4ff_effect_slot_valid_active(ctx, id)) {
1094 SDL_UnlockMutex(ctx->mutex);
1095 return;
1096 }
1097
1098 state = &ctx->states[id];
1099 STOP_EFFECT(state);
1100 state->allocated = false;
1101
1102 SDL_UnlockMutex(ctx->mutex);
1103}
1104
1105static bool SDL_HIDAPI_HapticDriverLg4ff_GetEffectStatus(SDL_HIDAPI_HapticDevice *device, int id)
1106{
1107 lg4ff_device *ctx = (lg4ff_device *)device->ctx;
1108 bool ret = false;
1109
1110 SDL_LockMutex(ctx->mutex);
1111 if (!lg4ff_effect_slot_valid_active(ctx, id)) {
1112 SDL_UnlockMutex(ctx->mutex);
1113 return false;
1114 }
1115
1116 if (test_bit(FF_EFFECT_STARTED, &ctx->states[id].flags)) {
1117 ret = true;
1118 }
1119 SDL_UnlockMutex(ctx->mutex);
1120
1121 return ret;
1122}
1123
1124static bool SDL_HIDAPI_HapticDriverLg4ff_SetGain(SDL_HIDAPI_HapticDevice *device, int gain)
1125{
1126 lg4ff_device *ctx = (lg4ff_device *)device->ctx;
1127 if (gain > 100) {
1128 gain = 100;
1129 }
1130 if (gain < 0) {
1131 gain = 0;
1132 }
1133 ctx->app_gain = (65535 * gain) / 100;
1134 return true;
1135}
1136
1137/*
1138 *Ported*
1139 Original functions by:
1140 Simon Wood <simon@mungewell.org>
1141 Michal Malý <madcatxster@devoid-pointer.net> <madcatxster@gmail.com>
1142 lg4ff_set_autocenter_default lg4ff_set_autocenter_ffex
1143 `git blame v6.12 drivers/hid/hid-lg4ff.c`, https://github.com/torvalds/linux.git
1144*/
1145static bool SDL_HIDAPI_HapticDriverLg4ff_SetAutocenter(SDL_HIDAPI_HapticDevice *device, int autocenter)
1146{
1147 lg4ff_device *ctx = (lg4ff_device *)device->ctx;
1148 Uint8 cmd[7] = {0};
1149 bool ret;
1150
1151 if (autocenter < 0) {
1152 autocenter = 0;
1153 }
1154 if (autocenter > 100) {
1155 autocenter = 100;
1156 }
1157
1158 SDL_LockMutex(ctx->mutex);
1159 if (ctx->is_ffex) {
1160 int magnitude = (90 * autocenter) / 100;
1161
1162 cmd[0] = 0xfe;
1163 cmd[1] = 0x03;
1164 cmd[2] = (Uint8)((Uint16)magnitude >> 14);
1165 cmd[3] = (Uint8)((Uint16)magnitude >> 14);
1166 cmd[4] = (Uint8)magnitude;
1167
1168 ret = SDL_SendJoystickEffect(ctx->hid_handle, cmd, sizeof(cmd));
1169 if (!ret) {
1170 SDL_UnlockMutex(ctx->mutex);
1171 SDL_SetError("Failed sending autocenter command");
1172 return false;
1173 }
1174 } else {
1175 Uint32 expand_a;
1176 Uint32 expand_b;
1177 int magnitude = (65535 * autocenter) / 100;
1178
1179 // first disable
1180 cmd[0] = 0xf5;
1181
1182 ret = SDL_SendJoystickEffect(ctx->hid_handle, cmd, sizeof(cmd));
1183 if (!ret) {
1184 SDL_UnlockMutex(ctx->mutex);
1185 SDL_SetError("Failed sending autocenter disable command");
1186 return false;
1187 }
1188
1189 if (magnitude == 0) {
1190 SDL_UnlockMutex(ctx->mutex);
1191 return true;
1192 }
1193
1194 // set strength
1195 if (magnitude <= 0xaaaa) {
1196 expand_a = 0x0c * magnitude;
1197 expand_b = 0x80 * magnitude;
1198 } else {
1199 expand_a = (0x0c * 0xaaaa) + 0x06 * (magnitude - 0xaaaa);
1200 expand_b = (0x80 * 0xaaaa) + 0xff * (magnitude - 0xaaaa);
1201 }
1202 expand_a = expand_a >> 1;
1203
1204 SDL_memset(cmd, 0x00, 7);
1205 cmd[0] = 0xfe;
1206 cmd[1] = 0x0d;
1207 cmd[2] = (Uint8)(expand_a / 0xaaaa);
1208 cmd[3] = (Uint8)(expand_a / 0xaaaa);
1209 cmd[4] = (Uint8)(expand_b / 0xaaaa);
1210
1211 ret = SDL_SendJoystickEffect(ctx->hid_handle, cmd, sizeof(cmd));
1212 if (!ret) {
1213 SDL_UnlockMutex(ctx->mutex);
1214 SDL_SetError("Failed sending autocenter magnitude command");
1215 return false;
1216 }
1217
1218 // enable
1219 SDL_memset(cmd, 0x00, 7);
1220 cmd[0] = 0x14;
1221
1222 ret = SDL_SendJoystickEffect(ctx->hid_handle, cmd, sizeof(cmd));
1223 if (!ret) {
1224 SDL_UnlockMutex(ctx->mutex);
1225 SDL_SetError("Failed sending autocenter enable command");
1226 return false;
1227 }
1228 }
1229 SDL_UnlockMutex(ctx->mutex);
1230 return true;
1231}
1232
1233static bool SDL_HIDAPI_HapticDriverLg4ff_Pause(SDL_HIDAPI_HapticDevice *device)
1234{
1235 return SDL_Unsupported();
1236}
1237
1238static bool SDL_HIDAPI_HapticDriverLg4ff_Resume(SDL_HIDAPI_HapticDevice *device)
1239{
1240 return SDL_Unsupported();
1241}
1242
1243SDL_HIDAPI_HapticDriver SDL_HIDAPI_HapticDriverLg4ff = {
1244 SDL_HIDAPI_HapticDriverLg4ff_JoystickSupported,
1245 SDL_HIDAPI_HapticDriverLg4ff_Open,
1246 SDL_HIDAPI_HapticDriverLg4ff_Close,
1247 SDL_HIDAPI_HapticDriverLg4ff_NumEffects,
1248 SDL_HIDAPI_HapticDriverLg4ff_NumEffects,
1249 SDL_HIDAPI_HapticDriverLg4ff_GetFeatures,
1250 SDL_HIDAPI_HapticDriverLg4ff_NumAxes,
1251 SDL_HIDAPI_HapticDriverLg4ff_CreateEffect,
1252 SDL_HIDAPI_HapticDriverLg4ff_UpdateEffect,
1253 SDL_HIDAPI_HapticDriverLg4ff_RunEffect,
1254 SDL_HIDAPI_HapticDriverLg4ff_StopEffect,
1255 SDL_HIDAPI_HapticDriverLg4ff_DestroyEffect,
1256 SDL_HIDAPI_HapticDriverLg4ff_GetEffectStatus,
1257 SDL_HIDAPI_HapticDriverLg4ff_SetGain,
1258 SDL_HIDAPI_HapticDriverLg4ff_SetAutocenter,
1259 SDL_HIDAPI_HapticDriverLg4ff_Pause,
1260 SDL_HIDAPI_HapticDriverLg4ff_Resume,
1261 SDL_HIDAPI_HapticDriverLg4ff_StopEffects,
1262};
1263
1264#endif //SDL_HAPTIC_HIDAPI_LG4FF
1265#endif //SDL_JOYSTICK_HIDAPI