text: /*
   * Plug and Play support for hid devices found through SDL2
   *
   * Copyright 2017 CodeWeavers, Aric Stewart
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation; either
   * version 2.1 of the License, or (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   * Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
   */
  
  #if 0
  #pragma makedep unix
  #endif
  
  #include "config.h"
  #include "wine/port.h"
  #include <errno.h>
  #include <fcntl.h>
  #include <stdarg.h>
  #include <stdlib.h>
  #ifdef HAVE_UNISTD_H
  # include <unistd.h>
  #endif
  #ifdef HAVE_SDL_H
  # include <SDL.h>
  #endif
  
  #include <pthread.h>
  
  #include "ntstatus.h"
  #define WIN32_NO_STATUS
  #include "windef.h"
  #include "winbase.h"
  #include "winnls.h"
  #include "winreg.h"
  #include "winternl.h"
  #include "ddk/wdm.h"
  #include "ddk/hidtypes.h"
  #include "ddk/hidsdi.h"
  #include "hidusage.h"
  
  #include "wine/debug.h"
  #include "wine/hid.h"
  #include "wine/unixlib.h"
  
  #include "unix_private.h"
  
  WINE_DEFAULT_DEBUG_CHANNEL(plugplay);
  
  #ifdef SONAME_LIBSDL2
  
  static pthread_mutex_t sdl_cs = PTHREAD_MUTEX_INITIALIZER;
  static struct sdl_bus_options options;
  
  static void *sdl_handle = NULL;
  static UINT quit_event = -1;
  static struct list event_queue = LIST_INIT(event_queue);
  static struct list device_list = LIST_INIT(device_list);
  
  #define MAKE_FUNCPTR(f) static typeof(f) * p##f = NULL
  MAKE_FUNCPTR(SDL_GetError);
  MAKE_FUNCPTR(SDL_Init);
  MAKE_FUNCPTR(SDL_JoystickClose);
  MAKE_FUNCPTR(SDL_JoystickEventState);
  MAKE_FUNCPTR(SDL_JoystickGetGUID);
  MAKE_FUNCPTR(SDL_JoystickGetGUIDString);
  MAKE_FUNCPTR(SDL_JoystickInstanceID);
  MAKE_FUNCPTR(SDL_JoystickName);
  MAKE_FUNCPTR(SDL_JoystickNumAxes);
  MAKE_FUNCPTR(SDL_JoystickOpen);
  MAKE_FUNCPTR(SDL_WaitEvent);
  MAKE_FUNCPTR(SDL_JoystickNumButtons);
  MAKE_FUNCPTR(SDL_JoystickNumBalls);
  MAKE_FUNCPTR(SDL_JoystickNumHats);
  MAKE_FUNCPTR(SDL_JoystickGetAxis);
  MAKE_FUNCPTR(SDL_JoystickGetHat);
  MAKE_FUNCPTR(SDL_IsGameController);
  MAKE_FUNCPTR(SDL_GameControllerClose);
  MAKE_FUNCPTR(SDL_GameControllerGetAxis);
  MAKE_FUNCPTR(SDL_GameControllerGetButton);
  MAKE_FUNCPTR(SDL_GameControllerName);
  MAKE_FUNCPTR(SDL_GameControllerOpen);
  MAKE_FUNCPTR(SDL_GameControllerEventState);
  MAKE_FUNCPTR(SDL_HapticClose);
  MAKE_FUNCPTR(SDL_HapticDestroyEffect);
  MAKE_FUNCPTR(SDL_HapticNewEffect);
  MAKE_FUNCPTR(SDL_HapticOpenFromJoystick);
  MAKE_FUNCPTR(SDL_HapticPause);
  MAKE_FUNCPTR(SDL_HapticQuery);
  MAKE_FUNCPTR(SDL_HapticRumbleInit);
  MAKE_FUNCPTR(SDL_HapticRumblePlay);
  MAKE_FUNCPTR(SDL_HapticRumbleSupported);
  MAKE_FUNCPTR(SDL_HapticRunEffect);
  MAKE_FUNCPTR(SDL_HapticSetGain);
  MAKE_FUNCPTR(SDL_HapticStopAll);
  MAKE_FUNCPTR(SDL_HapticStopEffect);
  MAKE_FUNCPTR(SDL_HapticUnpause);
  MAKE_FUNCPTR(SDL_HapticUpdateEffect);
  MAKE_FUNCPTR(SDL_JoystickIsHaptic);
  MAKE_FUNCPTR(SDL_GameControllerAddMapping);
  MAKE_FUNCPTR(SDL_RegisterEvents);
  MAKE_FUNCPTR(SDL_PushEvent);
  static int (*pSDL_JoystickRumble)(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms);
  static Uint16 (*pSDL_JoystickGetProduct)(SDL_Joystick * joystick);
  static Uint16 (*pSDL_JoystickGetProductVersion)(SDL_Joystick * joystick);
  static Uint16 (*pSDL_JoystickGetVendor)(SDL_Joystick * joystick);
  
  /* internal bits for extended rumble support, SDL_Haptic types are 16-bits */
  #define WINE_SDL_JOYSTICK_RUMBLE  0x40000000 /* using SDL_JoystickRumble API */
  #define WINE_SDL_HAPTIC_RUMBLE    0x80000000 /* using SDL_HapticRumble API */
  
  #define EFFECT_SUPPORT_HAPTICS  (SDL_HAPTIC_LEFTRIGHT|WINE_SDL_HAPTIC_RUMBLE|WINE_SDL_JOYSTICK_RUMBLE)
  #define EFFECT_SUPPORT_PHYSICAL (SDL_HAPTIC_CONSTANT|SDL_HAPTIC_RAMP|SDL_HAPTIC_SINE|SDL_HAPTIC_TRIANGLE| \
                                   SDL_HAPTIC_SAWTOOTHUP|SDL_HAPTIC_SAWTOOTHDOWN|SDL_HAPTIC_SPRING|SDL_HAPTIC_DAMPER|SDL_HAPTIC_INERTIA| \
                                   SDL_HAPTIC_FRICTION|SDL_HAPTIC_CUSTOM)
  
  struct sdl_device
  {
      struct unix_device unix_device;
  
      SDL_Joystick *sdl_joystick;
      SDL_GameController *sdl_controller;
      SDL_JoystickID id;
  
      DWORD effect_support;
      SDL_Haptic *sdl_haptic;
      int haptic_effect_id;
      int effect_ids[256];
  };
  
  static inline struct sdl_device *impl_from_unix_device(struct unix_device *iface)
  {
      return CONTAINING_RECORD(iface, struct sdl_device, unix_device);
  }
  
  static struct sdl_device *find_device_from_id(SDL_JoystickID id)
  {
      struct sdl_device *impl;
  
      LIST_FOR_EACH_ENTRY(impl, &device_list, struct sdl_device, unix_device.entry)
          if (impl->id == id) return impl;
  
      return NULL;
  }
  
  static void set_hat_value(struct unix_device *iface, int index, int value)
  {
      LONG x = 0, y = 0;
      switch (value)
      {
      case SDL_HAT_CENTERED: break;
      case SDL_HAT_DOWN: y = 1; break;
      case SDL_HAT_RIGHTDOWN: y = x = 1; break;
      case SDL_HAT_RIGHT: x = 1; break;
      case SDL_HAT_RIGHTUP: x = 1; y = -1; break;
      case SDL_HAT_UP: y = -1; break;
      case SDL_HAT_LEFTUP: x = y = -1; break;
      case SDL_HAT_LEFT: x = -1; break;
      case SDL_HAT_LEFTDOWN: x = -1; y = 1; break;
      }
      hid_device_set_hatswitch_x(iface, index, x);
      hid_device_set_hatswitch_y(iface, index, y);
  }
  
  static BOOL descriptor_add_haptic(struct sdl_device *impl)
  {
      USHORT i, count = 0;
      USAGE usages[16];
  
      if (!pSDL_JoystickIsHaptic(impl->sdl_joystick) ||
          !(impl->sdl_haptic = pSDL_HapticOpenFromJoystick(impl->sdl_joystick)))
          impl->effect_support = 0;
      else
      {
          impl->effect_support = pSDL_HapticQuery(impl->sdl_haptic);
          if (pSDL_HapticRumbleSupported(impl->sdl_haptic))
              impl->effect_support |= WINE_SDL_HAPTIC_RUMBLE;
  
          pSDL_HapticStopAll(impl->sdl_haptic);
          pSDL_HapticRumbleInit(impl->sdl_haptic);
      }
  
      if (!(impl->effect_support & EFFECT_SUPPORT_HAPTICS) && pSDL_JoystickRumble &&
          !pSDL_JoystickRumble(impl->sdl_joystick, 0, 0, 0))
          impl->effect_support |= WINE_SDL_JOYSTICK_RUMBLE;
  
      if (impl->effect_support & EFFECT_SUPPORT_HAPTICS)
      {
          if (!hid_device_add_haptics(&impl->unix_device))
              return FALSE;
      }
  
      if ((impl->effect_support & EFFECT_SUPPORT_PHYSICAL))
      {
          /* SDL_HAPTIC_SQUARE doesn't exist */
          if (impl->effect_support & SDL_HAPTIC_SINE) usages[count++] = PID_USAGE_ET_SINE;
          if (impl->effect_support & SDL_HAPTIC_TRIANGLE) usages[count++] = PID_USAGE_ET_TRIANGLE;
          if (impl->effect_support & SDL_HAPTIC_SAWTOOTHUP) usages[count++] = PID_USAGE_ET_SAWTOOTH_UP;
          if (impl->effect_support & SDL_HAPTIC_SAWTOOTHDOWN) usages[count++] = PID_USAGE_ET_SAWTOOTH_DOWN;
          if (impl->effect_support & SDL_HAPTIC_SPRING) usages[count++] = PID_USAGE_ET_SPRING;
          if (impl->effect_support & SDL_HAPTIC_DAMPER) usages[count++] = PID_USAGE_ET_DAMPER;
          if (impl->effect_support & SDL_HAPTIC_INERTIA) usages[count++] = PID_USAGE_ET_INERTIA;
          if (impl->effect_support & SDL_HAPTIC_FRICTION) usages[count++] = PID_USAGE_ET_FRICTION;
          if (impl->effect_support & SDL_HAPTIC_CONSTANT) usages[count++] = PID_USAGE_ET_CONSTANT_FORCE;
          if (impl->effect_support & SDL_HAPTIC_RAMP) usages[count++] = PID_USAGE_ET_RAMP;
  
          if (!hid_device_add_physical(&impl->unix_device, usages, count))
              return FALSE;
      }
  
      impl->haptic_effect_id = -1;
      for (i = 0; i < ARRAY_SIZE(impl->effect_ids); ++i) impl->effect_ids[i] = -1;
      return TRUE;
  }
  
  static NTSTATUS build_joystick_report_descriptor(struct unix_device *iface)
  {
      static const USAGE_AND_PAGE absolute_usages[] =
      {
          {.UsagePage = HID_USAGE_PAGE_GENERIC,    .Usage = HID_USAGE_GENERIC_X},
          {.UsagePage = HID_USAGE_PAGE_GENERIC,    .Usage = HID_USAGE_GENERIC_Y},
          {.UsagePage = HID_USAGE_PAGE_GENERIC,    .Usage = HID_USAGE_GENERIC_Z},
          {.UsagePage = HID_USAGE_PAGE_GENERIC,    .Usage = HID_USAGE_GENERIC_RX},
          {.UsagePage = HID_USAGE_PAGE_GENERIC,    .Usage = HID_USAGE_GENERIC_RY},
          {.UsagePage = HID_USAGE_PAGE_GENERIC,    .Usage = HID_USAGE_GENERIC_RZ},
          {.UsagePage = HID_USAGE_PAGE_SIMULATION, .Usage = HID_USAGE_SIMULATION_THROTTLE},
          {.UsagePage = HID_USAGE_PAGE_SIMULATION, .Usage = HID_USAGE_SIMULATION_RUDDER},
          {.UsagePage = HID_USAGE_PAGE_GENERIC,    .Usage = HID_USAGE_GENERIC_WHEEL},
          {.UsagePage = HID_USAGE_PAGE_SIMULATION, .Usage = HID_USAGE_SIMULATION_ACCELERATOR},
          {.UsagePage = HID_USAGE_PAGE_SIMULATION, .Usage = HID_USAGE_SIMULATION_BRAKE},
      };
      static const USAGE_AND_PAGE relative_usages[] =
      {
          {.UsagePage = HID_USAGE_PAGE_GENERIC, .Usage = HID_USAGE_GENERIC_X},
          {.UsagePage = HID_USAGE_PAGE_GENERIC, .Usage = HID_USAGE_GENERIC_Y},
          {.UsagePage = HID_USAGE_PAGE_GENERIC, .Usage = HID_USAGE_GENERIC_RX},
          {.UsagePage = HID_USAGE_PAGE_GENERIC, .Usage = HID_USAGE_GENERIC_RY},
          {.UsagePage = HID_USAGE_PAGE_GENERIC, .Usage = HID_USAGE_GENERIC_Z},
          {.UsagePage = HID_USAGE_PAGE_GENERIC, .Usage = HID_USAGE_GENERIC_RZ},
          {.UsagePage = HID_USAGE_PAGE_GENERIC, .Usage = HID_USAGE_GENERIC_SLIDER},
          {.UsagePage = HID_USAGE_PAGE_GENERIC, .Usage = HID_USAGE_GENERIC_DIAL},
          {.UsagePage = HID_USAGE_PAGE_GENERIC, .Usage = HID_USAGE_GENERIC_WHEEL},
      };
      struct sdl_device *impl = impl_from_unix_device(iface);
      int i, button_count, axis_count, ball_count, hat_count;
  
      axis_count = pSDL_JoystickNumAxes(impl->sdl_joystick);
      if (axis_count > ARRAY_SIZE(absolute_usages))
      {
          FIXME("More than %zu absolute axes found, ignoring.\n", ARRAY_SIZE(absolute_usages));
          axis_count = ARRAY_SIZE(absolute_usages);
      }
  
      ball_count = pSDL_JoystickNumBalls(impl->sdl_joystick);
      if (ball_count > ARRAY_SIZE(relative_usages) / 2)
      {
          FIXME("More than %zu relative axes found, ignoring.\n", ARRAY_SIZE(relative_usages));
          ball_count = ARRAY_SIZE(relative_usages) / 2;
      }
  
      hat_count = pSDL_JoystickNumHats(impl->sdl_joystick);
      button_count = pSDL_JoystickNumButtons(impl->sdl_joystick);
  
      if (!hid_device_begin_report_descriptor(iface, HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_JOYSTICK))
          return STATUS_NO_MEMORY;
  
      if (!hid_device_begin_input_report(iface))
          return STATUS_NO_MEMORY;
  
      for (i = 0; i < axis_count; i++)
      {
          if (!hid_device_add_axes(iface, 1, absolute_usages[i].UsagePage,
                                   &absolute_usages[i].Usage, FALSE, -32768, 32767))
              return STATUS_NO_MEMORY;
      }
  
      for (i = 0; i < ball_count; i++)
      {
          if (!hid_device_add_axes(iface, 2, relative_usages[2 * i].UsagePage,
                                   &relative_usages[2 * i].Usage, TRUE, INT32_MIN, INT32_MAX))
              return STATUS_NO_MEMORY;
      }
  
      if (hat_count && !hid_device_add_hatswitch(iface, hat_count))
          return STATUS_NO_MEMORY;
  
      if (button_count && !hid_device_add_buttons(iface, HID_USAGE_PAGE_BUTTON, 1, button_count))
          return STATUS_NO_MEMORY;
  
      if (!hid_device_end_input_report(iface))
          return STATUS_NO_MEMORY;
  
      if (!descriptor_add_haptic(impl))
          return STATUS_NO_MEMORY;
  
      if (!hid_device_end_report_descriptor(iface))
          return STATUS_NO_MEMORY;
  
      /* Initialize axis in the report */
      for (i = 0; i < axis_count; i++)
          hid_device_set_abs_axis(iface, i, pSDL_JoystickGetAxis(impl->sdl_joystick, i));
      for (i = 0; i < hat_count; i++)
          set_hat_value(iface, i, pSDL_JoystickGetHat(impl->sdl_joystick, i));
  
      return STATUS_SUCCESS;
  }
  
  static NTSTATUS build_controller_report_descriptor(struct unix_device *iface)
  {
      static const USAGE left_axis_usages[] = {HID_USAGE_GENERIC_X, HID_USAGE_GENERIC_Y};
      static const USAGE right_axis_usages[] = {HID_USAGE_GENERIC_RX, HID_USAGE_GENERIC_RY};
      static const USAGE trigger_axis_usages[] = {HID_USAGE_GENERIC_Z, HID_USAGE_GENERIC_RZ};
      struct sdl_device *impl = impl_from_unix_device(iface);
      ULONG i, button_count = SDL_CONTROLLER_BUTTON_MAX - 1;
      C_ASSERT(SDL_CONTROLLER_AXIS_MAX == 6);
  
      if (!hid_device_begin_report_descriptor(iface, HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_GAMEPAD))
          return STATUS_NO_MEMORY;
  
      if (!hid_device_begin_input_report(iface))
          return STATUS_NO_MEMORY;
  
      if (!hid_device_add_axes(iface, 2, HID_USAGE_PAGE_GENERIC, left_axis_usages,
                               FALSE, -32768, 32767))
          return STATUS_NO_MEMORY;
  
      if (!hid_device_add_axes(iface, 2, HID_USAGE_PAGE_GENERIC, right_axis_usages,
                               FALSE, -32768, 32767))
          return STATUS_NO_MEMORY;
  
      if (!hid_device_add_axes(iface, 2, HID_USAGE_PAGE_GENERIC, trigger_axis_usages,
                               FALSE, 0, 32767))
          return STATUS_NO_MEMORY;
  
      if (!hid_device_add_hatswitch(iface, 1))
          return STATUS_NO_MEMORY;
  
      if (!hid_device_add_buttons(iface, HID_USAGE_PAGE_BUTTON, 1, button_count))
          return STATUS_NO_MEMORY;
  
      if (!hid_device_end_input_report(iface))
          return STATUS_NO_MEMORY;
  
      if (!descriptor_add_haptic(impl))
          return STATUS_NO_MEMORY;
  
      if (!hid_device_end_report_descriptor(iface))
          return STATUS_NO_MEMORY;
  
      /* Initialize axis in the report */
      for (i = SDL_CONTROLLER_AXIS_LEFTX; i < SDL_CONTROLLER_AXIS_MAX; i++)
          hid_device_set_abs_axis(iface, i, pSDL_GameControllerGetAxis(impl->sdl_controller, i));
      if (pSDL_GameControllerGetButton(impl->sdl_controller, SDL_CONTROLLER_BUTTON_DPAD_UP))
          hid_device_set_hatswitch_y(iface, 0, -1);
      if (pSDL_GameControllerGetButton(impl->sdl_controller, SDL_CONTROLLER_BUTTON_DPAD_DOWN))
          hid_device_set_hatswitch_y(iface, 0, +1);
      if (pSDL_GameControllerGetButton(impl->sdl_controller, SDL_CONTROLLER_BUTTON_DPAD_LEFT))
          hid_device_set_hatswitch_x(iface, 0, -1);
      if (pSDL_GameControllerGetButton(impl->sdl_controller, SDL_CONTROLLER_BUTTON_DPAD_RIGHT))
          hid_device_set_hatswitch_x(iface, 0, +1);
  
      return STATUS_SUCCESS;
  }
  
  static void sdl_device_destroy(struct unix_device *iface)
  {
  }
  
  static NTSTATUS sdl_device_start(struct unix_device *iface)
  {
      struct sdl_device *impl = impl_from_unix_device(iface);
      if (impl->sdl_controller) return build_controller_report_descriptor(iface);
      return build_joystick_report_descriptor(iface);
  }
  
  static void sdl_device_stop(struct unix_device *iface)
  {
      struct sdl_device *impl = impl_from_unix_device(iface);
  
      pSDL_JoystickClose(impl->sdl_joystick);
      if (impl->sdl_controller) pSDL_GameControllerClose(impl->sdl_controller);
      if (impl->sdl_haptic) pSDL_HapticClose(impl->sdl_haptic);
  
      pthread_mutex_lock(&sdl_cs);
      list_remove(&impl->unix_device.entry);
      pthread_mutex_unlock(&sdl_cs);
  }
  
  NTSTATUS sdl_device_haptics_start(struct unix_device *iface, DWORD duration_ms,
                                    USHORT rumble_intensity, USHORT buzz_intensity)
  {
      struct sdl_device *impl = impl_from_unix_device(iface);
      SDL_HapticEffect effect;
  
      TRACE("iface %p, duration_ms %u, rumble_intensity %u, buzz_intensity %u.\n", iface, duration_ms,
            rumble_intensity, buzz_intensity);
  
      if (!(impl->effect_support & EFFECT_SUPPORT_HAPTICS)) return STATUS_NOT_SUPPORTED;
  
      memset(&effect, 0, sizeof(SDL_HapticEffect));
      effect.type = SDL_HAPTIC_LEFTRIGHT;
      effect.leftright.length = duration_ms;
      effect.leftright.large_magnitude = rumble_intensity;
      effect.leftright.small_magnitude = buzz_intensity;
  
      if (impl->effect_support & WINE_SDL_JOYSTICK_RUMBLE)
          pSDL_JoystickRumble(impl->sdl_joystick, 0, 0, 0);
      else if (impl->sdl_haptic)
          pSDL_HapticStopAll(impl->sdl_haptic);
      if (!effect.leftright.large_magnitude && !effect.leftright.small_magnitude)
          return STATUS_SUCCESS;
  
      if (impl->effect_support & SDL_HAPTIC_LEFTRIGHT)
      {
          if (impl->haptic_effect_id >= 0)
              pSDL_HapticDestroyEffect(impl->sdl_haptic, impl->haptic_effect_id);
          impl->haptic_effect_id = pSDL_HapticNewEffect(impl->sdl_haptic, &effect);
          if (impl->haptic_effect_id >= 0)
              pSDL_HapticRunEffect(impl->sdl_haptic, impl->haptic_effect_id, 1);
      }
      else if (impl->effect_support & WINE_SDL_HAPTIC_RUMBLE)
      {
          float magnitude = (effect.leftright.large_magnitude + effect.leftright.small_magnitude) / 2.0 / 32767.0;
          pSDL_HapticRumblePlay(impl->sdl_haptic, magnitude, effect.leftright.length);
      }
      else if (impl->effect_support & WINE_SDL_JOYSTICK_RUMBLE)
      {
          pSDL_JoystickRumble(impl->sdl_joystick, effect.leftright.large_magnitude,
                              effect.leftright.small_magnitude, duration_ms);
      }
  
      return STATUS_SUCCESS;
  }
  
  static NTSTATUS sdl_device_physical_device_control(struct unix_device *iface, USAGE control)
  {
      struct sdl_device *impl = impl_from_unix_device(iface);
      unsigned int i;
  
      TRACE("iface %p, control %#04x.\n", iface, control);
  
      switch (control)
      {
      case PID_USAGE_DC_ENABLE_ACTUATORS:
          pSDL_HapticSetGain(impl->sdl_haptic, 100);
          return STATUS_SUCCESS;
      case PID_USAGE_DC_DISABLE_ACTUATORS:
          pSDL_HapticSetGain(impl->sdl_haptic, 0);
          return STATUS_SUCCESS;
      case PID_USAGE_DC_STOP_ALL_EFFECTS:
          pSDL_HapticStopAll(impl->sdl_haptic);
          return STATUS_SUCCESS;
      case PID_USAGE_DC_DEVICE_RESET:
          pSDL_HapticStopAll(impl->sdl_haptic);
          for (i = 0; i < ARRAY_SIZE(impl->effect_ids); ++i)
          {
              if (impl->effect_ids[i] < 0) continue;
              pSDL_HapticDestroyEffect(impl->sdl_haptic, impl->effect_ids[i]);
              impl->effect_ids[i] = -1;
          }
          return STATUS_SUCCESS;
      case PID_USAGE_DC_DEVICE_PAUSE:
          pSDL_HapticPause(impl->sdl_haptic);
          return STATUS_SUCCESS;
      case PID_USAGE_DC_DEVICE_CONTINUE:
          pSDL_HapticUnpause(impl->sdl_haptic);
          return STATUS_SUCCESS;
      }
  
      return STATUS_NOT_SUPPORTED;
  }
  
  static NTSTATUS sdl_device_physical_effect_control(struct unix_device *iface, BYTE index,
                                                     USAGE control, BYTE iterations)
  {
      struct sdl_device *impl = impl_from_unix_device(iface);
      int id = impl->effect_ids[index];
  
      TRACE("iface %p, index %u, control %04x, iterations %u.\n", iface, index, control, iterations);
  
      if (impl->effect_ids[index] < 0) return STATUS_UNSUCCESSFUL;
  
      switch (control)
      {
      case PID_USAGE_OP_EFFECT_START_SOLO:
          pSDL_HapticStopAll(impl->sdl_haptic);
          /* fallthrough */
      case PID_USAGE_OP_EFFECT_START:
          pSDL_HapticRunEffect(impl->sdl_haptic, id, iterations);
          break;
      case PID_USAGE_OP_EFFECT_STOP:
          pSDL_HapticStopEffect(impl->sdl_haptic, id);
          break;
      }
  
      return STATUS_SUCCESS;
  }
  
  static NTSTATUS set_effect_type_from_usage(SDL_HapticEffect *effect, USAGE type)
  {
      switch (type)
      {
      case PID_USAGE_ET_SINE:
          effect->type = SDL_HAPTIC_SINE;
          return STATUS_SUCCESS;
      case PID_USAGE_ET_TRIANGLE:
          effect->type = SDL_HAPTIC_TRIANGLE;
          return STATUS_SUCCESS;
      case PID_USAGE_ET_SAWTOOTH_UP:
          effect->type = SDL_HAPTIC_SAWTOOTHUP;
          return STATUS_SUCCESS;
      case PID_USAGE_ET_SAWTOOTH_DOWN:
          effect->type = SDL_HAPTIC_SAWTOOTHDOWN;
          return STATUS_SUCCESS;
      case PID_USAGE_ET_SPRING:
          effect->type = SDL_HAPTIC_SPRING;
          return STATUS_SUCCESS;
      case PID_USAGE_ET_DAMPER:
          effect->type = SDL_HAPTIC_DAMPER;
          return STATUS_SUCCESS;
      case PID_USAGE_ET_INERTIA:
          effect->type = SDL_HAPTIC_INERTIA;
          return STATUS_SUCCESS;
      case PID_USAGE_ET_FRICTION:
          effect->type = SDL_HAPTIC_FRICTION;
          return STATUS_SUCCESS;
      case PID_USAGE_ET_CONSTANT_FORCE:
          effect->type = SDL_HAPTIC_CONSTANT;
          return STATUS_SUCCESS;
      case PID_USAGE_ET_RAMP:
          effect->type = SDL_HAPTIC_RAMP;
          return STATUS_SUCCESS;
      case PID_USAGE_ET_CUSTOM_FORCE_DATA:
          effect->type = SDL_HAPTIC_CUSTOM;
          return STATUS_SUCCESS;
      default:
          return STATUS_NOT_SUPPORTED;
      }
  }
  
  static NTSTATUS sdl_device_physical_effect_update(struct unix_device *iface, BYTE index,
                                                    struct effect_params *params)
  {
      struct sdl_device *impl = impl_from_unix_device(iface);
      int id = impl->effect_ids[index];
      SDL_HapticEffect effect = {0};
      NTSTATUS status;
  
      TRACE("iface %p, index %u, params %p.\n", iface, index, params);
  
      if ((status = set_effect_type_from_usage(&effect, params->effect_type))) return status;
  
      switch (params->effect_type)
      {
      case PID_USAGE_ET_SINE:
      case PID_USAGE_ET_SQUARE:
      case PID_USAGE_ET_TRIANGLE:
      case PID_USAGE_ET_SAWTOOTH_UP:
      case PID_USAGE_ET_SAWTOOTH_DOWN:
          effect.periodic.length = params->duration;
          effect.periodic.delay = params->start_delay;
          effect.periodic.button = params->trigger_button;
          effect.periodic.interval = params->trigger_repeat_interval;
          effect.periodic.direction.type = SDL_HAPTIC_SPHERICAL;
          effect.periodic.direction.dir[0] = params->direction[0] * 36000 / 256;
          effect.periodic.direction.dir[1] = params->direction[1] * 36000 / 256;
          effect.periodic.period = params->periodic.period;
          effect.periodic.magnitude = params->periodic.magnitude * 128;
          effect.periodic.offset = params->periodic.offset;
          effect.periodic.phase = params->periodic.phase;
          effect.periodic.attack_length = params->envelope.attack_time;
          effect.periodic.attack_level = params->envelope.attack_level;
          effect.periodic.fade_length = params->envelope.fade_time;
          effect.periodic.fade_level = params->envelope.fade_level;
          break;
  
      case PID_USAGE_ET_SPRING:
      case PID_USAGE_ET_DAMPER:
      case PID_USAGE_ET_INERTIA:
      case PID_USAGE_ET_FRICTION:
          effect.condition.length = params->duration;
          effect.condition.delay = params->start_delay;
          effect.condition.button = params->trigger_button;
          effect.condition.interval = params->trigger_repeat_interval;
          effect.condition.direction.type = SDL_HAPTIC_SPHERICAL;
          effect.condition.direction.dir[0] = params->direction[0] * 36000 / 256;
          effect.condition.direction.dir[1] = params->direction[1] * 36000 / 256;
          if (params->condition_count >= 1)
          {
              effect.condition.right_sat[0] = params->condition[0].positive_saturation;
              effect.condition.left_sat[0] = params->condition[0].negative_saturation;
              effect.condition.right_coeff[0] = params->condition[0].positive_coefficient;
              effect.condition.left_coeff[0] = params->condition[0].negative_coefficient;
              effect.condition.deadband[0] = params->condition[0].dead_band;
              effect.condition.center[0] = params->condition[0].center_point_offset;
          }
          if (params->condition_count >= 2)
          {
              effect.condition.right_sat[1] = params->condition[1].positive_saturation;
              effect.condition.left_sat[1] = params->condition[1].negative_saturation;
              effect.condition.right_coeff[1] = params->condition[1].positive_coefficient;
              effect.condition.left_coeff[1] = params->condition[1].negative_coefficient;
              effect.condition.deadband[1] = params->condition[1].dead_band;
              effect.condition.center[1] = params->condition[1].center_point_offset;
          }
          break;
  
      case PID_USAGE_ET_CONSTANT_FORCE:
          effect.constant.length = params->duration;
          effect.constant.delay = params->start_delay;
          effect.constant.button = params->trigger_button;
          effect.constant.interval = params->trigger_repeat_interval;
          effect.constant.direction.type = SDL_HAPTIC_SPHERICAL;
          effect.constant.direction.dir[0] = params->direction[0] * 36000 / 256;
          effect.constant.direction.dir[1] = params->direction[1] * 36000 / 256;
          effect.constant.level = params->constant_force.magnitude;
          effect.constant.attack_length = params->envelope.attack_time;
          effect.constant.attack_level = params->envelope.attack_level;
          effect.constant.fade_length = params->envelope.fade_time;
          effect.constant.fade_level = params->envelope.fade_level;
          break;
  
      case PID_USAGE_ET_RAMP:
          effect.ramp.length = params->duration;
          effect.ramp.delay = params->start_delay;
          effect.ramp.button = params->trigger_button;
          effect.ramp.interval = params->trigger_repeat_interval;
          effect.ramp.direction.type = SDL_HAPTIC_SPHERICAL;
          effect.ramp.direction.dir[0] = params->direction[0] * 36000 / 256;
          effect.ramp.direction.dir[1] = params->direction[1] * 36000 / 256;
          effect.ramp.start = params->ramp_force.ramp_start;
          effect.ramp.end = params->ramp_force.ramp_end;
          effect.ramp.attack_length = params->envelope.attack_time;
          effect.ramp.attack_level = params->envelope.attack_level;
          effect.ramp.fade_length = params->envelope.fade_time;
          effect.ramp.fade_level = params->envelope.fade_level;
          break;
  
      case PID_USAGE_ET_CUSTOM_FORCE_DATA:
          FIXME("not implemented!\n");
          break;
      }
  
      if (id < 0) impl->effect_ids[index] = pSDL_HapticNewEffect(impl->sdl_haptic, &effect);
      else pSDL_HapticUpdateEffect(impl->sdl_haptic, id, &effect);
  
      return STATUS_SUCCESS;
  }
  
  static const struct hid_device_vtbl sdl_device_vtbl =
  {
      sdl_device_destroy,
      sdl_device_start,
      sdl_device_stop,
      sdl_device_haptics_start,
      sdl_device_physical_device_control,
      sdl_device_physical_effect_control,
      sdl_device_physical_effect_update,
  };
  
  static BOOL set_report_from_joystick_event(struct sdl_device *impl, SDL_Event *event)
  {
      struct unix_device *iface = &impl->unix_device;
      struct hid_device_state *state = &iface->hid_device_state;
  
      if (impl->sdl_controller) return TRUE; /* use controller events instead */
  
      switch(event->type)
      {
          case SDL_JOYBUTTONDOWN:
          case SDL_JOYBUTTONUP:
          {
              SDL_JoyButtonEvent *ie = &event->jbutton;
  
              hid_device_set_button(iface, ie->button, ie->state);
              bus_event_queue_input_report(&event_queue, iface, state->report_buf, state->report_len);
              break;
          }
          case SDL_JOYAXISMOTION:
          {
              SDL_JoyAxisEvent *ie = &event->jaxis;
  
              hid_device_set_abs_axis(iface, ie->axis, ie->value);
              bus_event_queue_input_report(&event_queue, iface, state->report_buf, state->report_len);
              break;
          }
          case SDL_JOYBALLMOTION:
          {
              SDL_JoyBallEvent *ie = &event->jball;
  
              hid_device_set_rel_axis(iface, 2 * ie->ball, ie->xrel);
              hid_device_set_rel_axis(iface, 2 * ie->ball + 1, ie->yrel);
              bus_event_queue_input_report(&event_queue, iface, state->report_buf, state->report_len);
              break;
          }
          case SDL_JOYHATMOTION:
          {
              SDL_JoyHatEvent *ie = &event->jhat;
  
              set_hat_value(iface, ie->hat, ie->value);
              bus_event_queue_input_report(&event_queue, iface, state->report_buf, state->report_len);
              break;
          }
          default:
              ERR("TODO: Process Report (0x%x)\n",event->type);
      }
      return FALSE;
  }
  
  static BOOL set_report_from_controller_event(struct sdl_device *impl, SDL_Event *event)
  {
      struct unix_device *iface = &impl->unix_device;
      struct hid_device_state *state = &iface->hid_device_state;
  
      switch(event->type)
      {
          case SDL_CONTROLLERBUTTONDOWN:
          case SDL_CONTROLLERBUTTONUP:
          {
              SDL_ControllerButtonEvent *ie = &event->cbutton;
              int button;
  
              switch ((button = ie->button))
              {
              case SDL_CONTROLLER_BUTTON_DPAD_UP:
                  hid_device_set_hatswitch_y(iface, 0, ie->state ? -1 : 0);
                  break;
              case SDL_CONTROLLER_BUTTON_DPAD_DOWN:
                  hid_device_set_hatswitch_y(iface, 0, ie->state ? +1 : 0);
                  break;
              case SDL_CONTROLLER_BUTTON_DPAD_LEFT:
                  hid_device_set_hatswitch_x(iface, 0, ie->state ? -1 : 0);
                  break;
              case SDL_CONTROLLER_BUTTON_DPAD_RIGHT:
                  hid_device_set_hatswitch_x(iface, 0, ie->state ? +1 : 0);
                  break;
              case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: button = 4; break;
              case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: button = 5; break;
              case SDL_CONTROLLER_BUTTON_BACK: button = 6; break;
              case SDL_CONTROLLER_BUTTON_START: button = 7; break;
              case SDL_CONTROLLER_BUTTON_LEFTSTICK: button = 8; break;
              case SDL_CONTROLLER_BUTTON_RIGHTSTICK: button = 9; break;
              case SDL_CONTROLLER_BUTTON_GUIDE: button = 10; break;
              }
  
              hid_device_set_button(iface, button, ie->state);
              bus_event_queue_input_report(&event_queue, iface, state->report_buf, state->report_len);
              break;
          }
          case SDL_CONTROLLERAXISMOTION:
          {
              SDL_ControllerAxisEvent *ie = &event->caxis;
  
              hid_device_set_abs_axis(iface, ie->axis, ie->value);
              bus_event_queue_input_report(&event_queue, iface, state->report_buf, state->report_len);
              break;
          }
          default:
              ERR("TODO: Process Report (%x)\n",event->type);
      }
      return FALSE;
  }
  
  static void sdl_add_device(unsigned int index)
  {
      struct device_desc desc =
      {
          .input = -1,
          .manufacturer = {'S','D','L',0},
          .serialnumber = {'0','0','0','0',0},
      };
      struct sdl_device *impl;
  
      SDL_Joystick* joystick;
      SDL_JoystickID id;
      SDL_JoystickGUID guid;
      SDL_GameController *controller = NULL;
      const char *str;
      char guid_str[33];
  
      if ((joystick = pSDL_JoystickOpen(index)) == NULL)
      {
          WARN("Unable to open sdl device %i: %s\n", index, pSDL_GetError());
          return;
      }
  
      if (options.map_controllers && pSDL_IsGameController(index))
          controller = pSDL_GameControllerOpen(index);
  
      if (controller) str = pSDL_GameControllerName(controller);
      else str = pSDL_JoystickName(joystick);
      if (str) ntdll_umbstowcs(str, strlen(str) + 1, desc.product, ARRAY_SIZE(desc.product));
  
      id = pSDL_JoystickInstanceID(joystick);
  
      if (pSDL_JoystickGetProductVersion != NULL) {
          desc.vid = pSDL_JoystickGetVendor(joystick);
          desc.pid = pSDL_JoystickGetProduct(joystick);
          desc.version = pSDL_JoystickGetProductVersion(joystick);
      }
      else
      {
          desc.vid = 0x01;
          desc.pid = pSDL_JoystickInstanceID(joystick) + 1;
          desc.version = 0;
      }
  
      guid = pSDL_JoystickGetGUID(joystick);
      pSDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str));
      ntdll_umbstowcs(guid_str, strlen(guid_str) + 1, desc.serialnumber, ARRAY_SIZE(desc.serialnumber));
  
      if (controller) desc.is_gamepad = TRUE;
      else
      {
          int button_count, axis_count;
  
          axis_count = pSDL_JoystickNumAxes(joystick);
          button_count = pSDL_JoystickNumButtons(joystick);
          desc.is_gamepad = (axis_count == 6  && button_count >= 14);
      }
  
      TRACE("%s id %d, desc %s.\n", controller ? "controller" : "joystick", id, debugstr_device_desc(&desc));
  
      if (!(impl = hid_device_create(&sdl_device_vtbl, sizeof(struct sdl_device)))) return;
      list_add_tail(&device_list, &impl->unix_device.entry);
      impl->sdl_joystick = joystick;
      impl->sdl_controller = controller;
      impl->id = id;
  
      bus_event_queue_device_created(&event_queue, &impl->unix_device, &desc);
  }
  
  static void process_device_event(SDL_Event *event)
  {
      struct sdl_device *impl;
      SDL_JoystickID id;
  
      TRACE("Received action %x\n", event->type);
  
      pthread_mutex_lock(&sdl_cs);
  
      if (event->type == SDL_JOYDEVICEADDED)
          sdl_add_device(((SDL_JoyDeviceEvent *)event)->which);
      else if (event->type == SDL_JOYDEVICEREMOVED)
      {
          id = ((SDL_JoyDeviceEvent *)event)->which;
          impl = find_device_from_id(id);
          if (impl) bus_event_queue_device_removed(&event_queue, &impl->unix_device);
          else WARN("failed to find device with id %d\n", id);
      }
      else if (event->type >= SDL_JOYAXISMOTION && event->type <= SDL_JOYBUTTONUP)
      {
          id = ((SDL_JoyButtonEvent *)event)->which;
          impl = find_device_from_id(id);
          if (impl) set_report_from_joystick_event(impl, event);
          else WARN("failed to find device with id %d\n", id);
      }
      else if (event->type >= SDL_CONTROLLERAXISMOTION && event->type <= SDL_CONTROLLERBUTTONUP)
      {
          id = ((SDL_ControllerButtonEvent *)event)->which;
          impl = find_device_from_id(id);
          if (impl) set_report_from_controller_event(impl, event);
          else WARN("failed to find device with id %d\n", id);
      }
  
      pthread_mutex_unlock(&sdl_cs);
  }
  
  NTSTATUS sdl_bus_init(void *args)
  {
      const char *mapping;
      int i;
  
      TRACE("args %p\n", args);
  
      options = *(struct sdl_bus_options *)args;
  
      if (!(sdl_handle = dlopen(SONAME_LIBSDL2, RTLD_NOW)))
      {
          WARN("could not load %s\n", SONAME_LIBSDL2);
          return STATUS_UNSUCCESSFUL;
      }
  #define LOAD_FUNCPTR(f)                          \
      if ((p##f = dlsym(sdl_handle, #f)) == NULL)  \
      {                                            \
          WARN("could not find symbol %s\n", #f);  \
          goto failed;                             \
      }
      LOAD_FUNCPTR(SDL_GetError);
      LOAD_FUNCPTR(SDL_Init);
      LOAD_FUNCPTR(SDL_JoystickClose);
      LOAD_FUNCPTR(SDL_JoystickEventState);
      LOAD_FUNCPTR(SDL_JoystickGetGUID);
      LOAD_FUNCPTR(SDL_JoystickGetGUIDString);
      LOAD_FUNCPTR(SDL_JoystickInstanceID);
      LOAD_FUNCPTR(SDL_JoystickName);
      LOAD_FUNCPTR(SDL_JoystickNumAxes);
      LOAD_FUNCPTR(SDL_JoystickOpen);
      LOAD_FUNCPTR(SDL_WaitEvent);
      LOAD_FUNCPTR(SDL_JoystickNumButtons);
      LOAD_FUNCPTR(SDL_JoystickNumBalls);
      LOAD_FUNCPTR(SDL_JoystickNumHats);
      LOAD_FUNCPTR(SDL_JoystickGetAxis);
      LOAD_FUNCPTR(SDL_JoystickGetHat);
      LOAD_FUNCPTR(SDL_IsGameController);
      LOAD_FUNCPTR(SDL_GameControllerClose);
      LOAD_FUNCPTR(SDL_GameControllerGetAxis);
      LOAD_FUNCPTR(SDL_GameControllerGetButton);
      LOAD_FUNCPTR(SDL_GameControllerName);
      LOAD_FUNCPTR(SDL_GameControllerOpen);
      LOAD_FUNCPTR(SDL_GameControllerEventState);
      LOAD_FUNCPTR(SDL_HapticClose);
      LOAD_FUNCPTR(SDL_HapticDestroyEffect);
      LOAD_FUNCPTR(SDL_HapticNewEffect);
      LOAD_FUNCPTR(SDL_HapticOpenFromJoystick);
      LOAD_FUNCPTR(SDL_HapticPause);
      LOAD_FUNCPTR(SDL_HapticQuery);
      LOAD_FUNCPTR(SDL_HapticRumbleInit);
      LOAD_FUNCPTR(SDL_HapticRumblePlay);
      LOAD_FUNCPTR(SDL_HapticRumbleSupported);
      LOAD_FUNCPTR(SDL_HapticRunEffect);
      LOAD_FUNCPTR(SDL_HapticSetGain);
      LOAD_FUNCPTR(SDL_HapticStopAll);
      LOAD_FUNCPTR(SDL_HapticStopEffect);
      LOAD_FUNCPTR(SDL_HapticUnpause);
      LOAD_FUNCPTR(SDL_HapticUpdateEffect);
      LOAD_FUNCPTR(SDL_JoystickIsHaptic);
      LOAD_FUNCPTR(SDL_GameControllerAddMapping);
      LOAD_FUNCPTR(SDL_RegisterEvents);
      LOAD_FUNCPTR(SDL_PushEvent);
  #undef LOAD_FUNCPTR
      pSDL_JoystickRumble = dlsym(sdl_handle, "SDL_JoystickRumble");
      pSDL_JoystickGetProduct = dlsym(sdl_handle, "SDL_JoystickGetProduct");
      pSDL_JoystickGetProductVersion = dlsym(sdl_handle, "SDL_JoystickGetProductVersion");
      pSDL_JoystickGetVendor = dlsym(sdl_handle, "SDL_JoystickGetVendor");
  
      if (pSDL_Init(SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) < 0)
      {
          ERR("could not init SDL: %s\n", pSDL_GetError());
          goto failed;
      }
  
      if ((quit_event = pSDL_RegisterEvents(1)) == -1)
      {
          ERR("error registering quit event\n");
          goto failed;
      }
  
      pSDL_JoystickEventState(SDL_ENABLE);
      pSDL_GameControllerEventState(SDL_ENABLE);
  
      /* Process mappings */
      if (pSDL_GameControllerAddMapping)
      {
          if ((mapping = getenv("SDL_GAMECONTROLLERCONFIG")))
          {
              TRACE("Setting environment mapping %s\n", debugstr_a(mapping));
              if (pSDL_GameControllerAddMapping(mapping) < 0)
                  WARN("Failed to add environment mapping %s\n", pSDL_GetError());
          }
          else for (i = 0; i < options.mappings_count; ++i)
          {
              TRACE("Setting registry mapping %s\n", debugstr_a(options.mappings[i]));
              if (pSDL_GameControllerAddMapping(options.mappings[i]) < 0)
                  WARN("Failed to add registry mapping %s\n", pSDL_GetError());
          }
      }
  
      return STATUS_SUCCESS;
  
  failed:
      dlclose(sdl_handle);
      sdl_handle = NULL;
      return STATUS_UNSUCCESSFUL;
  }
  
  NTSTATUS sdl_bus_wait(void *args)
  {
      struct bus_event *result = args;
      SDL_Event event;
  
      /* cleanup previously returned event */
      bus_event_cleanup(result);
  
      do
      {
          if (bus_event_queue_pop(&event_queue, result)) return STATUS_PENDING;
          if (pSDL_WaitEvent(&event) != 0) process_device_event(&event);
          else WARN("SDL_WaitEvent failed: %s\n", pSDL_GetError());
      } while (event.type != quit_event);
  
      TRACE("SDL main loop exiting\n");
      bus_event_queue_destroy(&event_queue);
      dlclose(sdl_handle);
      sdl_handle = NULL;
      return STATUS_SUCCESS;
  }
  
  NTSTATUS sdl_bus_stop(void *args)
  {
      SDL_Event event;
  
      if (!sdl_handle) return STATUS_SUCCESS;
  
      event.type = quit_event;
      if (pSDL_PushEvent(&event) != 1)
      {
          ERR("error pushing quit event\n");
          return STATUS_UNSUCCESSFUL;
      }
  
      return STATUS_SUCCESS;
  }
  
  #else
  
  NTSTATUS sdl_bus_init(void *args)
  {
      WARN("SDL support not compiled in!\n");
      return STATUS_NOT_IMPLEMENTED;
  }
  
  NTSTATUS sdl_bus_wait(void *args)
  {
      WARN("SDL support not compiled in!\n");
      return STATUS_NOT_IMPLEMENTED;
  }
  
  NTSTATUS sdl_bus_stop(void *args)
  {
      WARN("SDL support not compiled in!\n");
      return STATUS_NOT_IMPLEMENTED;
  }
  
  #endif /* SONAME_LIBSDL2 */
  