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 | |
47 | static 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 | |
65 | struct 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 | |
83 | struct lg4ff_effect_parameters { |
84 | Sint32 level; |
85 | Sint32 d1; |
86 | Sint32 d2; |
87 | Sint32 k1; |
88 | Sint32 k2; |
89 | Uint32 clip; |
90 | }; |
91 | |
92 | struct 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 | |
101 | typedef 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 | |
128 | static 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 | |
149 | static SDL_INLINE Sint32 abs32(Sint32 x) { |
150 | return x < 0 ? -x : x; |
151 | } |
152 | static SDL_INLINE Sint64 abs64(Sint64 x) { |
153 | return x < 0 ? -x : x; |
154 | } |
155 | |
156 | static 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 | |
166 | static 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 |
174 | static 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 | |
227 | static 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 | |
250 | static 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 | |
277 | static 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 | */ |
306 | static 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 | */ |
338 | static 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 | */ |
369 | static 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 | */ |
430 | static 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 | */ |
459 | static 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 | */ |
492 | static 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 | */ |
540 | static 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 | */ |
557 | static 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 | */ |
572 | static 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 | */ |
682 | static 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(¶meters, 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], ¶meters); |
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 | */ |
726 | static 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, ¶meters[1]); |
791 | break; |
792 | case SDL_HAPTIC_DAMPER: |
793 | lg4ff_calculate_resistance(state, ¶meters[2]); |
794 | break; |
795 | case SDL_HAPTIC_FRICTION: |
796 | lg4ff_calculate_resistance(state, ¶meters[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, ¶meters[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 | |
833 | static 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 | |
848 | static 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 | |
862 | static 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 | */ |
886 | static 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 | |
938 | static 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 | |
953 | static 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 | |
967 | static int SDL_HIDAPI_HapticDriverLg4ff_NumEffects(SDL_HIDAPI_HapticDevice *device) |
968 | { |
969 | return LG4FF_MAX_EFFECTS; |
970 | } |
971 | |
972 | static 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 | |
989 | static 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 | |
994 | static int SDL_HIDAPI_HapticDriverLg4ff_NumAxes(SDL_HIDAPI_HapticDevice *device) |
995 | { |
996 | return 1; |
997 | } |
998 | |
999 | static 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 |
1035 | static 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 | |
1046 | static 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 | |
1064 | static 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 | |
1082 | static bool SDL_HIDAPI_HapticDriverLg4ff_StopEffect(SDL_HIDAPI_HapticDevice *device, int id) |
1083 | { |
1084 | return SDL_HIDAPI_HapticDriverLg4ff_RunEffect(device, id, 0); |
1085 | } |
1086 | |
1087 | static 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 | |
1105 | static 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 | |
1124 | static 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 | */ |
1145 | static 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 | |
1233 | static bool SDL_HIDAPI_HapticDriverLg4ff_Pause(SDL_HIDAPI_HapticDevice *device) |
1234 | { |
1235 | return SDL_Unsupported(); |
1236 | } |
1237 | |
1238 | static bool SDL_HIDAPI_HapticDriverLg4ff_Resume(SDL_HIDAPI_HapticDevice *device) |
1239 | { |
1240 | return SDL_Unsupported(); |
1241 | } |
1242 | |
1243 | SDL_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 |