msi: Implement MSIMODIFY_MERGE function in TABLE_modify.
[wine.git] / dlls / dinput / effect_linuxinput.c
blobca43b171a2195ea1080bc3d6b45af7a7e22e0080
1 /* DirectInput Linux Event Device Effect
3 * Copyright 2005 Daniel Remenak
5 * Thanks to Google's Summer of Code Program (2005)
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22 #include "config.h"
24 #ifdef HAVE_STRUCT_FF_EFFECT_DIRECTION
26 #include <stdarg.h>
27 #include <string.h>
28 #ifdef HAVE_LINUX_INPUT_H
29 # include <linux/input.h>
30 # undef SW_MAX
31 #endif
32 #include <limits.h>
33 #include <errno.h>
34 #ifdef HAVE_UNISTD_H
35 # include <unistd.h>
36 #endif
37 #include <math.h>
38 #include "wine/debug.h"
39 #include "wine/unicode.h"
40 #include "windef.h"
41 #include "winbase.h"
42 #include "winerror.h"
43 #include "dinput.h"
45 #include "device_private.h"
47 WINE_DEFAULT_DEBUG_CHANNEL(dinput);
49 static const IDirectInputEffectVtbl LinuxInputEffectVtbl;
50 typedef struct LinuxInputEffectImpl LinuxInputEffectImpl;
51 struct LinuxInputEffectImpl
53 IDirectInputEffect IDirectInputEffect_iface;
54 LONG ref;
55 GUID guid;
57 struct ff_effect effect; /* Effect data */
58 int gain; /* Effect gain */
59 int first_axis_is_x;
60 int* fd; /* Parent device */
61 struct list *entry; /* Entry into the parent's list of effects */
64 static inline LinuxInputEffectImpl *impl_from_IDirectInputEffect(IDirectInputEffect *iface)
66 return CONTAINING_RECORD(iface, LinuxInputEffectImpl, IDirectInputEffect_iface);
69 /******************************************************************************
70 * DirectInputEffect Functional Helper
73 static DWORD _typeFromGUID(REFGUID guid)
75 if (IsEqualGUID(guid, &GUID_ConstantForce)) {
76 return DIEFT_CONSTANTFORCE;
77 } else if (IsEqualGUID(guid, &GUID_Square)
78 || IsEqualGUID(guid, &GUID_Sine)
79 || IsEqualGUID(guid, &GUID_Triangle)
80 || IsEqualGUID(guid, &GUID_SawtoothUp)
81 || IsEqualGUID(guid, &GUID_SawtoothDown)) {
82 return DIEFT_PERIODIC;
83 } else if (IsEqualGUID(guid, &GUID_RampForce)) {
84 return DIEFT_RAMPFORCE;
85 } else if (IsEqualGUID(guid, &GUID_Spring)
86 || IsEqualGUID(guid, &GUID_Damper)
87 || IsEqualGUID(guid, &GUID_Inertia)
88 || IsEqualGUID(guid, &GUID_Friction)) {
89 return DIEFT_CONDITION;
90 } else if (IsEqualGUID(guid, &GUID_CustomForce)) {
91 return DIEFT_CUSTOMFORCE;
92 } else {
93 WARN("GUID (%s) is not a known force type\n", _dump_dinput_GUID(guid));
94 return 0;
99 /******************************************************************************
100 * DirectInputEffect debug helpers
103 static void _dump_DIEFFECT_flags(DWORD dwFlags)
105 if (TRACE_ON(dinput)) {
106 unsigned int i;
107 static const struct {
108 DWORD mask;
109 const char *name;
110 } flags[] = {
111 #define FE(x) { x, #x}
112 FE(DIEFF_CARTESIAN),
113 FE(DIEFF_OBJECTIDS),
114 FE(DIEFF_OBJECTOFFSETS),
115 FE(DIEFF_POLAR),
116 FE(DIEFF_SPHERICAL)
117 #undef FE
119 for (i = 0; i < (sizeof(flags) / sizeof(flags[0])); i++)
120 if (flags[i].mask & dwFlags)
121 TRACE("%s ", flags[i].name);
122 TRACE("\n");
126 static void _dump_DIENVELOPE(LPCDIENVELOPE env)
128 if (env->dwSize != sizeof(DIENVELOPE)) {
129 WARN("Non-standard DIENVELOPE structure size %d.\n", env->dwSize);
131 TRACE("Envelope has attack (level: %d time: %d), fade (level: %d time: %d)\n",
132 env->dwAttackLevel, env->dwAttackTime, env->dwFadeLevel, env->dwFadeTime);
135 static void _dump_DICONSTANTFORCE(LPCDICONSTANTFORCE frc)
137 TRACE("Constant force has magnitude %d\n", frc->lMagnitude);
140 static void _dump_DIPERIODIC(LPCDIPERIODIC frc)
142 TRACE("Periodic force has magnitude %d, offset %d, phase %d, period %d\n",
143 frc->dwMagnitude, frc->lOffset, frc->dwPhase, frc->dwPeriod);
146 static void _dump_DIRAMPFORCE(LPCDIRAMPFORCE frc)
148 TRACE("Ramp force has start %d, end %d\n",
149 frc->lStart, frc->lEnd);
152 static void _dump_DICONDITION(LPCDICONDITION frc)
154 TRACE("Condition has offset %d, pos/neg coefficients %d and %d, pos/neg saturations %d and %d, deadband %d\n",
155 frc->lOffset, frc->lPositiveCoefficient, frc->lNegativeCoefficient,
156 frc->dwPositiveSaturation, frc->dwNegativeSaturation, frc->lDeadBand);
159 static void _dump_DICUSTOMFORCE(LPCDICUSTOMFORCE frc)
161 unsigned int i;
162 TRACE("Custom force uses %d channels, sample period %d. Has %d samples at %p.\n",
163 frc->cChannels, frc->dwSamplePeriod, frc->cSamples, frc->rglForceData);
164 if (frc->cSamples % frc->cChannels != 0)
165 WARN("Custom force has a non-integral samples-per-channel count!\n");
166 if (TRACE_ON(dinput)) {
167 TRACE("Custom force data (time aligned, axes in order):\n");
168 for (i = 1; i <= frc->cSamples; ++i) {
169 TRACE("%d ", frc->rglForceData[i]);
170 if (i % frc->cChannels == 0)
171 TRACE("\n");
176 static void _dump_DIEFFECT(LPCDIEFFECT eff, REFGUID guid)
178 unsigned int i;
179 DWORD type = _typeFromGUID(guid);
181 TRACE("Dumping DIEFFECT structure:\n");
182 TRACE(" - dwSize: %d\n", eff->dwSize);
183 if ((eff->dwSize != sizeof(DIEFFECT)) && (eff->dwSize != sizeof(DIEFFECT_DX5))) {
184 WARN("Non-standard DIEFFECT structure size %d\n", eff->dwSize);
186 TRACE(" - dwFlags: %d\n", eff->dwFlags);
187 TRACE(" ");
188 _dump_DIEFFECT_flags(eff->dwFlags);
189 TRACE(" - dwDuration: %d\n", eff->dwDuration);
190 TRACE(" - dwGain: %d\n", eff->dwGain);
191 if (eff->dwGain > 10000)
192 WARN("dwGain is out of range (>10,000)\n");
193 TRACE(" - dwTriggerButton: %d\n", eff->dwTriggerButton);
194 TRACE(" - dwTriggerRepeatInterval: %d\n", eff->dwTriggerRepeatInterval);
195 TRACE(" - cAxes: %d\n", eff->cAxes);
196 TRACE(" - rgdwAxes: %p\n", eff->rgdwAxes);
197 if (TRACE_ON(dinput) && eff->rgdwAxes) {
198 TRACE(" ");
199 for (i = 0; i < eff->cAxes; ++i)
200 TRACE("%d ", eff->rgdwAxes[i]);
201 TRACE("\n");
203 TRACE(" - rglDirection: %p\n", eff->rglDirection);
204 TRACE(" - lpEnvelope: %p\n", eff->lpEnvelope);
205 TRACE(" - cbTypeSpecificParams: %d\n", eff->cbTypeSpecificParams);
206 TRACE(" - lpvTypeSpecificParams: %p\n", eff->lpvTypeSpecificParams);
207 if (eff->dwSize > sizeof(DIEFFECT_DX5))
208 TRACE(" - dwStartDelay: %d\n", eff->dwStartDelay);
209 if (eff->lpEnvelope != NULL)
210 _dump_DIENVELOPE(eff->lpEnvelope);
211 if (type == DIEFT_CONSTANTFORCE) {
212 if (eff->cbTypeSpecificParams != sizeof(DICONSTANTFORCE)) {
213 WARN("Effect claims to be a constant force but the type-specific params are the wrong size!\n");
214 } else {
215 _dump_DICONSTANTFORCE(eff->lpvTypeSpecificParams);
217 } else if (type == DIEFT_PERIODIC) {
218 if (eff->cbTypeSpecificParams != sizeof(DIPERIODIC)) {
219 WARN("Effect claims to be a periodic force but the type-specific params are the wrong size!\n");
220 } else {
221 _dump_DIPERIODIC(eff->lpvTypeSpecificParams);
223 } else if (type == DIEFT_RAMPFORCE) {
224 if (eff->cbTypeSpecificParams != sizeof(DIRAMPFORCE)) {
225 WARN("Effect claims to be a ramp force but the type-specific params are the wrong size!\n");
226 } else {
227 _dump_DIRAMPFORCE(eff->lpvTypeSpecificParams);
229 } else if (type == DIEFT_CONDITION) {
230 if (eff->cbTypeSpecificParams != sizeof(DICONDITION)) {
231 WARN("Effect claims to be a condition but the type-specific params are the wrong size!\n");
232 } else {
233 _dump_DICONDITION(eff->lpvTypeSpecificParams);
235 } else if (type == DIEFT_CUSTOMFORCE) {
236 if (eff->cbTypeSpecificParams != sizeof(DICUSTOMFORCE)) {
237 WARN("Effect claims to be a custom force but the type-specific params are the wrong size!\n");
238 } else {
239 _dump_DICUSTOMFORCE(eff->lpvTypeSpecificParams);
245 /******************************************************************************
246 * LinuxInputEffectImpl
249 static ULONG WINAPI LinuxInputEffectImpl_AddRef(
250 LPDIRECTINPUTEFFECT iface)
252 LinuxInputEffectImpl *This = impl_from_IDirectInputEffect(iface);
253 return InterlockedIncrement(&(This->ref));
256 static HRESULT WINAPI LinuxInputEffectImpl_Download(
257 LPDIRECTINPUTEFFECT iface)
259 LinuxInputEffectImpl *This = impl_from_IDirectInputEffect(iface);
261 TRACE("(this=%p)\n", This);
263 if (ioctl(*(This->fd), EVIOCSFF, &This->effect) == -1) {
264 if (errno == ENOMEM) {
265 return DIERR_DEVICEFULL;
266 } else {
267 FIXME("Could not upload effect. Assuming a disconnected device %d \"%s\".\n", *This->fd, strerror(errno));
268 return DIERR_INPUTLOST;
272 return DI_OK;
275 static HRESULT WINAPI LinuxInputEffectImpl_Escape(
276 LPDIRECTINPUTEFFECT iface,
277 LPDIEFFESCAPE pesc)
279 WARN("(this=%p,%p): invalid: no hardware-specific escape codes in this"
280 " driver!\n", iface, pesc);
282 return DI_OK;
285 static HRESULT WINAPI LinuxInputEffectImpl_GetEffectGuid(
286 LPDIRECTINPUTEFFECT iface,
287 LPGUID pguid)
289 LinuxInputEffectImpl *This = impl_from_IDirectInputEffect(iface);
291 TRACE("(this=%p,%p)\n", This, pguid);
293 *pguid = This->guid;
295 return DI_OK;
298 static HRESULT WINAPI LinuxInputEffectImpl_GetEffectStatus(
299 LPDIRECTINPUTEFFECT iface,
300 LPDWORD pdwFlags)
302 TRACE("(this=%p,%p)\n", iface, pdwFlags);
304 /* linux sends the effect status through an event.
305 * that event is trapped by our parent joystick driver
306 * and there is no clean way to pass it back to us. */
307 FIXME("Not enough information to provide a status.\n");
309 (*pdwFlags) = 0;
311 return DI_OK;
314 static HRESULT WINAPI LinuxInputEffectImpl_GetParameters(
315 LPDIRECTINPUTEFFECT iface,
316 LPDIEFFECT peff,
317 DWORD dwFlags)
319 HRESULT diErr = DI_OK;
320 LinuxInputEffectImpl *This = impl_from_IDirectInputEffect(iface);
321 TRACE("(this=%p,%p,%d)\n", This, peff, dwFlags);
323 /* Major conversion factors are:
324 * times: millisecond (linux) -> microsecond (windows) (x * 1000)
325 * forces: scale 0x7FFF (linux) -> scale 10000 (windows) approx ((x / 33) * 10)
326 * angles: scale 0x7FFF (linux) -> scale 35999 (windows) approx ((x / 33) * 36)
327 * angle bases: 0 -> -y (down) (linux) -> 0 -> +x (right) (windows)
330 if (dwFlags & DIEP_AXES) {
331 if (peff->cAxes < 2 /* linuxinput effects always use 2 axes, x and y */)
332 diErr = DIERR_MOREDATA;
333 peff->cAxes = 2;
334 if (diErr)
335 return diErr;
336 else {
337 peff->rgdwAxes[0] = DIJOFS_X;
338 peff->rgdwAxes[1] = DIJOFS_Y;
342 if (dwFlags & DIEP_DIRECTION) {
343 if (peff->cAxes < 2)
344 diErr = DIERR_MOREDATA;
345 peff->cAxes = 2;
346 if (diErr)
347 return diErr;
348 else {
349 if (peff->dwFlags & DIEFF_CARTESIAN) {
350 peff->rglDirection[0] = sin(M_PI * 3 * This->effect.direction / 0x7FFF) * 1000;
351 peff->rglDirection[1] = cos(M_PI * 3 * This->effect.direction / 0x7FFF) * 1000;
352 } else {
353 /* Polar and spherical coordinates are the same for two or less
354 * axes.
355 * Note that we also use this case if NO flags are marked.
356 * According to MSDN, we should return the direction in the
357 * format that it was specified in, if no flags are marked.
359 peff->rglDirection[0] = (This->effect.direction / 33) * 36 + 9000;
360 if (peff->rglDirection[0] > 35999)
361 peff->rglDirection[0] -= 35999;
366 if (dwFlags & DIEP_DURATION) {
367 peff->dwDuration = (DWORD)This->effect.replay.length * 1000;
370 if (dwFlags & DIEP_ENVELOPE) {
371 struct ff_envelope* env;
372 if (This->effect.type == FF_CONSTANT) env = &This->effect.u.constant.envelope;
373 else if (This->effect.type == FF_PERIODIC) env = &This->effect.u.periodic.envelope;
374 else if (This->effect.type == FF_RAMP) env = &This->effect.u.ramp.envelope;
375 else env = NULL;
376 if (env == NULL) {
377 peff->lpEnvelope = NULL;
378 } else if (peff->lpEnvelope == NULL) {
379 return DIERR_INVALIDPARAM;
380 } else {
381 peff->lpEnvelope->dwAttackLevel = (env->attack_level / 33) * 10;
382 peff->lpEnvelope->dwAttackTime = env->attack_length * 1000;
383 peff->lpEnvelope->dwFadeLevel = (env->fade_level / 33) * 10;
384 peff->lpEnvelope->dwFadeTime = env->fade_length * 1000;
388 if (dwFlags & DIEP_GAIN) {
389 peff->dwGain = This->gain * 10000 / 0xFFFF;
392 if (dwFlags & DIEP_SAMPLEPERIOD) {
393 /* the linux input ff driver has no support for setting
394 * the playback sample period. 0 means default. */
395 peff->dwSamplePeriod = 0;
398 if (dwFlags & DIEP_STARTDELAY) {
399 peff->dwStartDelay = This->effect.replay.delay * 1000;
402 if (dwFlags & DIEP_TRIGGERBUTTON) {
403 FIXME("LinuxInput button mapping needs redoing; for now, assuming we're using an actual joystick.\n");
404 peff->dwTriggerButton = DIJOFS_BUTTON(This->effect.trigger.button - BTN_JOYSTICK);
407 if (dwFlags & DIEP_TRIGGERREPEATINTERVAL) {
408 peff->dwTriggerRepeatInterval = This->effect.trigger.interval * 1000;
411 if (dwFlags & DIEP_TYPESPECIFICPARAMS) {
412 DWORD expectedsize = 0;
413 if (This->effect.type == FF_PERIODIC) {
414 expectedsize = sizeof(DIPERIODIC);
415 } else if (This->effect.type == FF_CONSTANT) {
416 expectedsize = sizeof(DICONSTANTFORCE);
417 } else if (This->effect.type == FF_SPRING
418 || This->effect.type == FF_FRICTION
419 || This->effect.type == FF_INERTIA
420 || This->effect.type == FF_DAMPER) {
421 expectedsize = sizeof(DICONDITION) * 2;
422 } else if (This->effect.type == FF_RAMP) {
423 expectedsize = sizeof(DIRAMPFORCE);
425 if (expectedsize > peff->cbTypeSpecificParams)
426 diErr = DIERR_MOREDATA;
427 peff->cbTypeSpecificParams = expectedsize;
428 if (diErr)
429 return diErr;
430 else {
431 if (This->effect.type == FF_PERIODIC) {
432 LPDIPERIODIC tsp = peff->lpvTypeSpecificParams;
433 tsp->dwMagnitude = (This->effect.u.periodic.magnitude / 33) * 10;
434 tsp->lOffset = (This->effect.u.periodic.offset / 33) * 10;
435 tsp->dwPhase = (This->effect.u.periodic.phase / 33) * 36;
436 tsp->dwPeriod = (This->effect.u.periodic.period * 1000);
437 } else if (This->effect.type == FF_CONSTANT) {
438 LPDICONSTANTFORCE tsp = peff->lpvTypeSpecificParams;
439 tsp->lMagnitude = (This->effect.u.constant.level / 33) * 10;
440 } else if (This->effect.type == FF_SPRING
441 || This->effect.type == FF_FRICTION
442 || This->effect.type == FF_INERTIA
443 || This->effect.type == FF_DAMPER) {
444 LPDICONDITION tsp = peff->lpvTypeSpecificParams;
445 int i;
446 for (i = 0; i < 2; ++i) {
447 tsp[i].lOffset = (This->effect.u.condition[i].center / 33) * 10;
448 tsp[i].lPositiveCoefficient = (This->effect.u.condition[i].right_coeff / 33) * 10;
449 tsp[i].lNegativeCoefficient = (This->effect.u.condition[i].left_coeff / 33) * 10;
450 tsp[i].dwPositiveSaturation = (This->effect.u.condition[i].right_saturation / 33) * 10;
451 tsp[i].dwNegativeSaturation = (This->effect.u.condition[i].left_saturation / 33) * 10;
452 tsp[i].lDeadBand = (This->effect.u.condition[i].deadband / 33) * 10;
454 } else if (This->effect.type == FF_RAMP) {
455 LPDIRAMPFORCE tsp = peff->lpvTypeSpecificParams;
456 tsp->lStart = (This->effect.u.ramp.start_level / 33) * 10;
457 tsp->lEnd = (This->effect.u.ramp.end_level / 33) * 10;
462 return diErr;
465 static HRESULT WINAPI LinuxInputEffectImpl_Initialize(
466 LPDIRECTINPUTEFFECT iface,
467 HINSTANCE hinst,
468 DWORD dwVersion,
469 REFGUID rguid)
471 FIXME("(this=%p,%p,%d,%s): stub!\n",
472 iface, hinst, dwVersion, debugstr_guid(rguid));
474 return DI_OK;
477 static HRESULT WINAPI LinuxInputEffectImpl_QueryInterface(
478 LPDIRECTINPUTEFFECT iface,
479 REFIID riid,
480 void **ppvObject)
482 LinuxInputEffectImpl *This = impl_from_IDirectInputEffect(iface);
484 TRACE("(this=%p,%s,%p)\n", This, debugstr_guid(riid), ppvObject);
486 if (IsEqualGUID(&IID_IUnknown, riid) ||
487 IsEqualGUID(&IID_IDirectInputEffect, riid)) {
488 LinuxInputEffectImpl_AddRef(iface);
489 *ppvObject = This;
490 return 0;
493 TRACE("Unsupported interface!\n");
494 return E_FAIL;
497 static HRESULT WINAPI LinuxInputEffectImpl_Start(
498 LPDIRECTINPUTEFFECT iface,
499 DWORD dwIterations,
500 DWORD dwFlags)
502 struct input_event event;
503 LinuxInputEffectImpl *This = impl_from_IDirectInputEffect(iface);
505 TRACE("(this=%p,%d,%d)\n", This, dwIterations, dwFlags);
507 if (!(dwFlags & DIES_NODOWNLOAD)) {
508 /* Download the effect if necessary */
509 if (This->effect.id == -1) {
510 HRESULT res = LinuxInputEffectImpl_Download(iface);
511 if (res != DI_OK)
512 return res;
516 if (dwFlags & DIES_SOLO) {
517 FIXME("Solo mode requested: should be stopping all effects here!\n");
520 event.type = EV_FF;
521 event.code = This->effect.id;
522 event.value = min( dwIterations, INT_MAX );
523 if (write(*(This->fd), &event, sizeof(event)) == -1) {
524 FIXME("Unable to write event. Assuming device disconnected.\n");
525 return DIERR_INPUTLOST;
528 return DI_OK;
531 static HRESULT WINAPI LinuxInputEffectImpl_SetParameters(
532 LPDIRECTINPUTEFFECT iface,
533 LPCDIEFFECT peff,
534 DWORD dwFlags)
536 LinuxInputEffectImpl *This = impl_from_IDirectInputEffect(iface);
537 DWORD type = _typeFromGUID(&This->guid);
538 HRESULT retval = DI_OK;
540 TRACE("(this=%p,%p,%d)\n", This, peff, dwFlags);
542 _dump_DIEFFECT(peff, &This->guid);
544 if ((dwFlags & ~DIEP_NORESTART & ~DIEP_NODOWNLOAD & ~DIEP_START) == 0) {
545 /* set everything */
546 dwFlags = DIEP_AXES | DIEP_DIRECTION | DIEP_DURATION | DIEP_ENVELOPE |
547 DIEP_GAIN | DIEP_SAMPLEPERIOD | DIEP_STARTDELAY | DIEP_TRIGGERBUTTON |
548 DIEP_TRIGGERREPEATINTERVAL | DIEP_TYPESPECIFICPARAMS;
551 if (dwFlags & DIEP_AXES) {
552 /* the linux input effect system only supports one or two axes */
553 if (peff->cAxes > 2)
554 return DIERR_INVALIDPARAM;
555 else if (peff->cAxes < 1)
556 return DIERR_INCOMPLETEEFFECT;
557 This->first_axis_is_x = peff->rgdwAxes[0] == DIJOFS_X;
560 /* some of this may look funky, but it's 'cause the linux driver and directx have
561 * different opinions about which way direction "0" is. directx has 0 along the x
562 * axis (left), linux has it along the y axis (down). */
563 if (dwFlags & DIEP_DIRECTION) {
564 if (peff->cAxes == 1) {
565 if (peff->dwFlags & DIEFF_CARTESIAN) {
566 if (dwFlags & DIEP_AXES) {
567 if (peff->rgdwAxes[0] == DIJOFS_X && peff->rglDirection[0] >= 0)
568 This->effect.direction = 0x4000;
569 else if (peff->rgdwAxes[0] == DIJOFS_X && peff->rglDirection[0] < 0)
570 This->effect.direction = 0xC000;
571 else if (peff->rgdwAxes[0] == DIJOFS_Y && peff->rglDirection[0] >= 0)
572 This->effect.direction = 0;
573 else if (peff->rgdwAxes[0] == DIJOFS_Y && peff->rglDirection[0] < 0)
574 This->effect.direction = 0x8000;
576 } else {
577 /* one-axis effects must use cartesian coords */
578 return DIERR_INVALIDPARAM;
580 } else { /* two axes */
581 if (peff->dwFlags & DIEFF_CARTESIAN) {
582 LONG x, y;
583 if (This->first_axis_is_x) {
584 x = peff->rglDirection[0];
585 y = peff->rglDirection[1];
586 } else {
587 x = peff->rglDirection[1];
588 y = peff->rglDirection[0];
590 This->effect.direction = (int)((3 * M_PI / 2 - atan2(y, x)) * -0x7FFF / M_PI);
591 } else {
592 /* Polar and spherical are the same for 2 axes */
593 /* Precision is important here, so we do double math with exact constants */
594 This->effect.direction = (int)(((double)peff->rglDirection[0] - 90) / 35999) * 0x7FFF;
599 if (dwFlags & DIEP_DURATION)
600 This->effect.replay.length = peff->dwDuration / 1000;
602 if (dwFlags & DIEP_ENVELOPE) {
603 struct ff_envelope* env;
604 if (This->effect.type == FF_CONSTANT) env = &This->effect.u.constant.envelope;
605 else if (This->effect.type == FF_PERIODIC) env = &This->effect.u.periodic.envelope;
606 else if (This->effect.type == FF_RAMP) env = &This->effect.u.ramp.envelope;
607 else env = NULL;
609 if (peff->lpEnvelope == NULL) {
610 /* if this type had an envelope, reset it */
611 if (env) {
612 env->attack_length = 0;
613 env->attack_level = 0;
614 env->fade_length = 0;
615 env->fade_level = 0;
617 } else {
618 /* did we get passed an envelope for a type that doesn't even have one? */
619 if (!env) return DIERR_INVALIDPARAM;
620 /* copy the envelope */
621 env->attack_length = peff->lpEnvelope->dwAttackTime / 1000;
622 env->attack_level = (peff->lpEnvelope->dwAttackLevel / 10) * 32;
623 env->fade_length = peff->lpEnvelope->dwFadeTime / 1000;
624 env->fade_level = (peff->lpEnvelope->dwFadeLevel / 10) * 32;
628 /* Gain and Sample Period settings are not supported by the linux
629 * event system */
630 if (dwFlags & DIEP_GAIN) {
631 This->gain = 0xFFFF * peff->dwGain / 10000;
632 TRACE("Effect gain requested but no effect gain functionality present.\n");
635 if (dwFlags & DIEP_SAMPLEPERIOD)
636 TRACE("Sample period requested but no sample period functionality present.\n");
638 if (dwFlags & DIEP_STARTDELAY)
639 This->effect.replay.delay = peff->dwStartDelay / 1000;
641 if (dwFlags & DIEP_TRIGGERBUTTON) {
642 if (peff->dwTriggerButton != -1) {
643 FIXME("Linuxinput button mapping needs redoing, assuming we're using a joystick.\n");
644 FIXME("Trigger button translation not yet implemented!\n");
646 This->effect.trigger.button = 0;
649 if (dwFlags & DIEP_TRIGGERREPEATINTERVAL)
650 This->effect.trigger.interval = peff->dwTriggerRepeatInterval / 1000;
652 if (dwFlags & DIEP_TYPESPECIFICPARAMS) {
653 if (!(peff->lpvTypeSpecificParams))
654 return DIERR_INCOMPLETEEFFECT;
655 if (type == DIEFT_PERIODIC) {
656 LPCDIPERIODIC tsp;
657 if (peff->cbTypeSpecificParams != sizeof(DIPERIODIC))
658 return DIERR_INVALIDPARAM;
659 tsp = peff->lpvTypeSpecificParams;
660 This->effect.u.periodic.magnitude = (tsp->dwMagnitude / 10) * 32;
661 This->effect.u.periodic.offset = (tsp->lOffset / 10) * 32;
662 This->effect.u.periodic.phase = (tsp->dwPhase / 9) * 8; /* == (/ 36 * 32) */
663 This->effect.u.periodic.period = tsp->dwPeriod / 1000;
664 } else if (type == DIEFT_CONSTANTFORCE) {
665 LPCDICONSTANTFORCE tsp;
666 if (peff->cbTypeSpecificParams != sizeof(DICONSTANTFORCE))
667 return DIERR_INVALIDPARAM;
668 tsp = peff->lpvTypeSpecificParams;
669 This->effect.u.constant.level = (max(min(tsp->lMagnitude, 10000), -10000) / 10) * 32;
670 } else if (type == DIEFT_RAMPFORCE) {
671 LPCDIRAMPFORCE tsp;
672 if (peff->cbTypeSpecificParams != sizeof(DIRAMPFORCE))
673 return DIERR_INVALIDPARAM;
674 tsp = peff->lpvTypeSpecificParams;
675 This->effect.u.ramp.start_level = (tsp->lStart / 10) * 32;
676 This->effect.u.ramp.end_level = (tsp->lEnd / 10) * 32;
677 } else if (type == DIEFT_CONDITION) {
678 LPCDICONDITION tsp = peff->lpvTypeSpecificParams;
679 if (peff->cbTypeSpecificParams == sizeof(DICONDITION)) {
680 /* One condition block. This needs to be rotated to direction,
681 * and expanded to separate x and y conditions. */
682 int i;
683 double factor[2];
684 factor[0] = asin((This->effect.direction * 3.0 * M_PI) / 0x7FFF);
685 factor[1] = acos((This->effect.direction * 3.0 * M_PI) / 0x7FFF);
686 for (i = 0; i < 2; ++i) {
687 This->effect.u.condition[i].center = (int)(factor[i] * (tsp->lOffset / 10) * 32);
688 This->effect.u.condition[i].right_coeff = (int)(factor[i] * (tsp->lPositiveCoefficient / 10) * 32);
689 This->effect.u.condition[i].left_coeff = (int)(factor[i] * (tsp->lNegativeCoefficient / 10) * 32);
690 This->effect.u.condition[i].right_saturation = (int)(factor[i] * (tsp->dwPositiveSaturation / 10) * 32);
691 This->effect.u.condition[i].left_saturation = (int)(factor[i] * (tsp->dwNegativeSaturation / 10) * 32);
692 This->effect.u.condition[i].deadband = (int)(factor[i] * (tsp->lDeadBand / 10) * 32);
694 } else if (peff->cbTypeSpecificParams == 2 * sizeof(DICONDITION)) {
695 /* Two condition blocks. Direct parameter copy. */
696 int i;
697 for (i = 0; i < 2; ++i) {
698 This->effect.u.condition[i].center = (tsp[i].lOffset / 10) * 32;
699 This->effect.u.condition[i].right_coeff = (tsp[i].lPositiveCoefficient / 10) * 32;
700 This->effect.u.condition[i].left_coeff = (tsp[i].lNegativeCoefficient / 10) * 32;
701 This->effect.u.condition[i].right_saturation = (tsp[i].dwPositiveSaturation / 10) * 32;
702 This->effect.u.condition[i].left_saturation = (tsp[i].dwNegativeSaturation / 10) * 32;
703 This->effect.u.condition[i].deadband = (tsp[i].lDeadBand / 10) * 32;
705 } else {
706 return DIERR_INVALIDPARAM;
708 } else {
709 FIXME("Custom force types are not supported\n");
710 return DIERR_INVALIDPARAM;
714 if (!(dwFlags & DIEP_NODOWNLOAD))
715 retval = LinuxInputEffectImpl_Download(iface);
716 if (retval != DI_OK)
717 return DI_DOWNLOADSKIPPED;
719 if (dwFlags & DIEP_NORESTART)
720 TRACE("DIEP_NORESTART: not handled (we have no control of that).\n");
722 if (dwFlags & DIEP_START)
723 retval = LinuxInputEffectImpl_Start(iface, 1, 0);
724 if (retval != DI_OK)
725 return retval;
727 return DI_OK;
730 static HRESULT WINAPI LinuxInputEffectImpl_Stop(
731 LPDIRECTINPUTEFFECT iface)
733 struct input_event event;
734 LinuxInputEffectImpl *This = impl_from_IDirectInputEffect(iface);
736 TRACE("(this=%p)\n", This);
738 event.type = EV_FF;
739 event.code = This->effect.id;
740 event.value = 0;
741 /* we don't care about the success or failure of this call */
742 write(*(This->fd), &event, sizeof(event));
744 return DI_OK;
747 static HRESULT WINAPI LinuxInputEffectImpl_Unload(
748 LPDIRECTINPUTEFFECT iface)
750 LinuxInputEffectImpl *This = impl_from_IDirectInputEffect(iface);
751 TRACE("(this=%p)\n", This);
753 /* Erase the downloaded effect */
754 if (ioctl(*(This->fd), EVIOCRMFF, This->effect.id) == -1)
755 return DIERR_INVALIDPARAM;
757 /* Mark the effect as deallocated */
758 This->effect.id = -1;
760 return DI_OK;
763 static ULONG WINAPI LinuxInputEffectImpl_Release(LPDIRECTINPUTEFFECT iface)
765 LinuxInputEffectImpl *This = impl_from_IDirectInputEffect(iface);
766 ULONG ref = InterlockedDecrement(&(This->ref));
768 if (ref == 0)
770 LinuxInputEffectImpl_Stop(iface);
771 LinuxInputEffectImpl_Unload(iface);
772 list_remove(This->entry);
773 HeapFree(GetProcessHeap(), 0, LIST_ENTRY(This->entry, effect_list_item, entry));
774 HeapFree(GetProcessHeap(), 0, This);
776 return ref;
779 /******************************************************************************
780 * LinuxInputEffect
783 DECLSPEC_HIDDEN HRESULT linuxinput_create_effect(
784 int* fd,
785 REFGUID rguid,
786 struct list *parent_list_entry,
787 LPDIRECTINPUTEFFECT* peff)
789 LinuxInputEffectImpl* newEffect = HeapAlloc(GetProcessHeap(),
790 HEAP_ZERO_MEMORY, sizeof(LinuxInputEffectImpl));
791 DWORD type = _typeFromGUID(rguid);
793 newEffect->IDirectInputEffect_iface.lpVtbl = &LinuxInputEffectVtbl;
794 newEffect->ref = 1;
795 newEffect->guid = *rguid;
796 newEffect->fd = fd;
797 newEffect->gain = 0xFFFF;
799 /* set the type. this cannot be changed over the effect's life. */
800 switch (type) {
801 case DIEFT_PERIODIC:
802 newEffect->effect.type = FF_PERIODIC;
803 if (IsEqualGUID(rguid, &GUID_Sine)) {
804 newEffect->effect.u.periodic.waveform = FF_SINE;
805 } else if (IsEqualGUID(rguid, &GUID_Triangle)) {
806 newEffect->effect.u.periodic.waveform = FF_TRIANGLE;
807 } else if (IsEqualGUID(rguid, &GUID_Square)) {
808 newEffect->effect.u.periodic.waveform = FF_SQUARE;
809 } else if (IsEqualGUID(rguid, &GUID_SawtoothUp)) {
810 newEffect->effect.u.periodic.waveform = FF_SAW_UP;
811 } else if (IsEqualGUID(rguid, &GUID_SawtoothDown)) {
812 newEffect->effect.u.periodic.waveform = FF_SAW_DOWN;
814 break;
815 case DIEFT_CONSTANTFORCE:
816 newEffect->effect.type = FF_CONSTANT;
817 break;
818 case DIEFT_RAMPFORCE:
819 newEffect->effect.type = FF_RAMP;
820 break;
821 case DIEFT_CONDITION:
822 if (IsEqualGUID(rguid, &GUID_Spring)) {
823 newEffect->effect.type = FF_SPRING;
824 } else if (IsEqualGUID(rguid, &GUID_Friction)) {
825 newEffect->effect.type = FF_FRICTION;
826 } else if (IsEqualGUID(rguid, &GUID_Inertia)) {
827 newEffect->effect.type = FF_INERTIA;
828 } else if (IsEqualGUID(rguid, &GUID_Damper)) {
829 newEffect->effect.type = FF_DAMPER;
831 break;
832 case DIEFT_CUSTOMFORCE:
833 FIXME("Custom forces are not supported.\n");
834 HeapFree(GetProcessHeap(), 0, newEffect);
835 return DIERR_INVALIDPARAM;
836 default:
837 FIXME("Unknown force type 0x%x.\n", type);
838 HeapFree(GetProcessHeap(), 0, newEffect);
839 return DIERR_INVALIDPARAM;
842 /* mark as non-uploaded */
843 newEffect->effect.id = -1;
845 newEffect->entry = parent_list_entry;
847 *peff = &newEffect->IDirectInputEffect_iface;
849 TRACE("Creating linux input system effect (%p) with guid %s\n",
850 *peff, _dump_dinput_GUID(rguid));
852 return DI_OK;
855 DECLSPEC_HIDDEN HRESULT linuxinput_get_info_A(
856 int fd,
857 REFGUID rguid,
858 LPDIEFFECTINFOA info)
860 DWORD type = _typeFromGUID(rguid);
862 TRACE("(%d, %s, %p) type=%d\n", fd, _dump_dinput_GUID(rguid), info, type);
864 if (!info) return E_POINTER;
866 if (info->dwSize != sizeof(DIEFFECTINFOA)) return DIERR_INVALIDPARAM;
868 info->guid = *rguid;
870 info->dwEffType = type;
871 /* the event device API does not support querying for all these things
872 * therefore we assume that we have support for them
873 * that's not as dangerous as it sounds, since drivers are allowed to
874 * ignore parameters they claim to support anyway */
875 info->dwEffType |= DIEFT_DEADBAND | DIEFT_FFATTACK | DIEFT_FFFADE
876 | DIEFT_POSNEGCOEFFICIENTS | DIEFT_POSNEGSATURATION
877 | DIEFT_SATURATION | DIEFT_STARTDELAY;
879 /* again, assume we have support for everything */
880 info->dwStaticParams = DIEP_ALLPARAMS;
881 info->dwDynamicParams = info->dwStaticParams;
883 /* yes, this is windows behavior (print the GUID_Name for name) */
884 strcpy(info->tszName, _dump_dinput_GUID(rguid));
886 return DI_OK;
889 DECLSPEC_HIDDEN HRESULT linuxinput_get_info_W(
890 int fd,
891 REFGUID rguid,
892 LPDIEFFECTINFOW info)
894 DWORD type = _typeFromGUID(rguid);
896 TRACE("(%d, %s, %p) type=%d\n", fd, _dump_dinput_GUID(rguid), info, type);
898 if (!info) return E_POINTER;
900 if (info->dwSize != sizeof(DIEFFECTINFOW)) return DIERR_INVALIDPARAM;
902 info->guid = *rguid;
904 info->dwEffType = type;
905 /* the event device API does not support querying for all these things
906 * therefore we assume that we have support for them
907 * that's not as dangerous as it sounds, since drivers are allowed to
908 * ignore parameters they claim to support anyway */
909 info->dwEffType |= DIEFT_DEADBAND | DIEFT_FFATTACK | DIEFT_FFFADE
910 | DIEFT_POSNEGCOEFFICIENTS | DIEFT_POSNEGSATURATION
911 | DIEFT_SATURATION | DIEFT_STARTDELAY;
913 /* again, assume we have support for everything */
914 info->dwStaticParams = DIEP_ALLPARAMS;
915 info->dwDynamicParams = info->dwStaticParams;
917 /* yes, this is windows behavior (print the GUID_Name for name) */
918 MultiByteToWideChar(CP_ACP, 0, _dump_dinput_GUID(rguid), -1,
919 info->tszName, MAX_PATH);
921 return DI_OK;
924 static const IDirectInputEffectVtbl LinuxInputEffectVtbl = {
925 LinuxInputEffectImpl_QueryInterface,
926 LinuxInputEffectImpl_AddRef,
927 LinuxInputEffectImpl_Release,
928 LinuxInputEffectImpl_Initialize,
929 LinuxInputEffectImpl_GetEffectGuid,
930 LinuxInputEffectImpl_GetParameters,
931 LinuxInputEffectImpl_SetParameters,
932 LinuxInputEffectImpl_Start,
933 LinuxInputEffectImpl_Stop,
934 LinuxInputEffectImpl_GetEffectStatus,
935 LinuxInputEffectImpl_Download,
936 LinuxInputEffectImpl_Unload,
937 LinuxInputEffectImpl_Escape
940 #endif /* HAVE_STRUCT_FF_EFFECT_DIRECTION */