Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/dom/gamepad/windows/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 35 kB image not shown  

Quelle  WindowsGamepad.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include <algorithm>
#include <cstddef>

#ifndef UNICODE
#  define UNICODE
#endif
#include <windows.h>
#include <hidsdi.h>
#include <stdio.h>
#include <xinput.h>

#include "nsITimer.h"
#include "nsTArray.h"
#include "nsThreadUtils.h"
#include "nsWindowsHelpers.h"

#include "mozilla/ArrayUtils.h"

#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/dom/GamepadPlatformService.h"
#include "mozilla/dom/GamepadRemapping.h"
#include "Gamepad.h"

namespace {

using namespace mozilla;
using namespace mozilla::dom;

// USB HID usage tables, page 1, 0x30 = X
const uint32_t kAxisMinimumUsageNumber = 0x30;
// USB HID usage tables, page 1 (Hat switch)
const uint32_t kAxesLengthCap = 16;

// USB HID usage tables
const uint32_t kDesktopUsagePage = 0x1;
const uint32_t kButtonUsagePage = 0x9;

// Multiple devices-changed notifications can be sent when a device
// is connected, because USB devices consist of multiple logical devices.
// Therefore, we wait a bit after receiving one before looking for
// device changes.
const uint32_t kDevicesChangedStableDelay = 200;
// Both DirectInput and XInput are polling-driven here,
// so we need to poll it periodically.
// 4ms, or 250 Hz, is consistent with Chrome's gamepad implementation.
const uint32_t kWindowsGamepadPollInterval = 4;

const UINT kRawInputError = (UINT)-1;

// In XInputGetState, we can't get the state of Xbox Guide button.
// We need to go through the undocumented XInputGetStateEx method
// to get that button's state.
const LPCSTR kXInputGetStateExOrdinal = (LPCSTR)100;
// Bitmask for the Guide button in XInputGamepadEx.wButtons.
const int XINPUT_GAMEPAD_Guide = 0x0400;

#ifndef XUSER_MAX_COUNT
#  define XUSER_MAX_COUNT 4
#endif

const struct {
  int usagePage;
  int usage;
} kUsagePages[] = {
    // USB HID usage tables, page 1
    {kDesktopUsagePage, 4},  // Joystick
    {kDesktopUsagePage, 5}   // Gamepad
};

const struct {
  WORD button;
  int mapped;
} kXIButtonMap[] = {{XINPUT_GAMEPAD_DPAD_UP, 12},
                    {XINPUT_GAMEPAD_DPAD_DOWN, 13},
                    {XINPUT_GAMEPAD_DPAD_LEFT, 14},
                    {XINPUT_GAMEPAD_DPAD_RIGHT, 15},
                    {XINPUT_GAMEPAD_START, 9},
                    {XINPUT_GAMEPAD_BACK, 8},
                    {XINPUT_GAMEPAD_LEFT_THUMB, 10},
                    {XINPUT_GAMEPAD_RIGHT_THUMB, 11},
                    {XINPUT_GAMEPAD_LEFT_SHOULDER, 4},
                    {XINPUT_GAMEPAD_RIGHT_SHOULDER, 5},
                    {XINPUT_GAMEPAD_Guide, 16},
                    {XINPUT_GAMEPAD_A, 0},
                    {XINPUT_GAMEPAD_B, 1},
                    {XINPUT_GAMEPAD_X, 2},
                    {XINPUT_GAMEPAD_Y, 3}};
const size_t kNumMappings = std::size(kXIButtonMap);

enum GamepadType { kNoGamepad = 0, kRawInputGamepad, kXInputGamepad };

class WindowsGamepadService;
// This pointer holds a windows gamepad backend service,
// it will be created and destroyed by background thread and
// used by gMonitorThread
WindowsGamepadService* MOZ_NON_OWNING_REF gService = nullptr;
MOZ_RUNINIT nsCOMPtr<nsIThread> gMonitorThread = nullptr;
static bool sIsShutdown = false;

class Gamepad {
 public:
  GamepadType type;

  // Handle to raw input device
  HANDLE handle;

  // XInput Index of the user's controller. Passed to XInputGetState.
  DWORD userIndex;

  // Last-known state of the controller.
  XINPUT_STATE state;

  // Handle from the GamepadService
  GamepadHandle gamepadHandle;

  // Information about the physical device.
  unsigned numAxes;
  unsigned numButtons;

  nsTArray<bool> buttons;
  struct axisValue {
    HIDP_VALUE_CAPS caps;
    double value;
    bool active;

    axisValue() : value(0.0f), active(false) {}
    explicit axisValue(const HIDP_VALUE_CAPS& aCaps)
        : caps(aCaps), value(0.0f), active(true) {}
  };
  nsTArray<axisValue> axes;

  RefPtr<GamepadRemapper> remapper;

  // Used during rescan to find devices that were disconnected.
  bool present;

  Gamepad(uint32_t aNumAxes, uint32_t aNumButtons, GamepadType aType)
      : type(aType), numAxes(aNumAxes), numButtons(aNumButtons), present(true) {
    buttons.SetLength(numButtons);
    axes.SetLength(numAxes);
  }

 private:
  Gamepad() {}
};

// Drop this in favor of decltype when we require a new enough SDK.
using XInputEnable_func = void(WINAPI*)(BOOL);

// RAII class to wrap loading the XInput DLL
class XInputLoader {
 public:
  XInputLoader()
      : module(nullptr), mXInputGetState(nullptr), mXInputEnable(nullptr) {
    // xinput1_4.dll exists on Windows 8
    // xinput9_1_0.dll exists on Windows 7 and Vista
    // xinput1_3.dll shipped with the DirectX SDK
    const wchar_t* dlls[] = {L"xinput1_4.dll", L"xinput9_1_0.dll",
                             L"xinput1_3.dll"};
    const size_t kNumDLLs = std::size(dlls);
    for (size_t i = 0; i < kNumDLLs; ++i) {
      module = LoadLibraryW(dlls[i]);
      if (module) {
        mXInputEnable = reinterpret_cast<XInputEnable_func>(
            GetProcAddress(module, "XInputEnable"));
        // Checking if `XInputGetStateEx` is available. If not,
        // we will fallback to use `XInputGetState`.
        mXInputGetState = reinterpret_cast<decltype(XInputGetState)*>(
            GetProcAddress(module, kXInputGetStateExOrdinal));
        if (!mXInputGetState) {
          mXInputGetState = reinterpret_cast<decltype(XInputGetState)*>(
              GetProcAddress(module, "XInputGetState"));
        }
        MOZ_ASSERT(mXInputGetState &&
                   "XInputGetState must be linked successfully.");

        if (mXInputEnable) {
          mXInputEnable(TRUE);
        }
        break;
      }
    }
  }

  ~XInputLoader() {
    mXInputEnable = nullptr;
    mXInputGetState = nullptr;

    if (module) {
      FreeLibrary(module);
    }
  }

  explicit operator bool() { return module && mXInputGetState; }

  HMODULE module;
  decltype(XInputGetState)* mXInputGetState;
  XInputEnable_func mXInputEnable;
};

bool GetPreparsedData(HANDLE handle, nsTArray<uint8_t>& data) {
  UINT size;
  if (GetRawInputDeviceInfo(handle, RIDI_PREPARSEDDATA, nullptr, &size) ==
      kRawInputError) {
    return false;
  }
  data.SetLength(size);
  return GetRawInputDeviceInfo(handle, RIDI_PREPARSEDDATA, data.Elements(),
                               &size) > 0;
}

/*
 * Given an axis value and a minimum and maximum range,
 * scale it to be in the range -1.0 .. 1.0.
 */

double ScaleAxis(ULONG value, LONG min, LONG max) {
  return 2.0 * (value - min) / (max - min) - 1.0;
}

/*
 * Return true if this USB HID usage page and usage are of a type we
 * know how to handle.
 */

bool SupportedUsage(USHORT page, USHORT usage) {
  for (unsigned i = 0; i < std::size(kUsagePages); i++) {
    if (page == kUsagePages[i].usagePage && usage == kUsagePages[i].usage) {
      return true;
    }
  }
  return false;
}

class HIDLoader {
 public:
  HIDLoader()
      : mHidD_GetProductString(nullptr),
        mHidP_GetCaps(nullptr),
        mHidP_GetButtonCaps(nullptr),
        mHidP_GetValueCaps(nullptr),
        mHidP_GetUsages(nullptr),
        mHidP_GetUsageValue(nullptr),
        mHidP_GetScaledUsageValue(nullptr),
        mModule(LoadLibraryW(L"hid.dll")) {
    if (mModule) {
      mHidD_GetProductString =
          reinterpret_cast<decltype(HidD_GetProductString)*>(
              GetProcAddress(mModule, "HidD_GetProductString"));
      mHidP_GetCaps = reinterpret_cast<decltype(HidP_GetCaps)*>(
          GetProcAddress(mModule, "HidP_GetCaps"));
      mHidP_GetButtonCaps = reinterpret_cast<decltype(HidP_GetButtonCaps)*>(
          GetProcAddress(mModule, "HidP_GetButtonCaps"));
      mHidP_GetValueCaps = reinterpret_cast<decltype(HidP_GetValueCaps)*>(
          GetProcAddress(mModule, "HidP_GetValueCaps"));
      mHidP_GetUsages = reinterpret_cast<decltype(HidP_GetUsages)*>(
          GetProcAddress(mModule, "HidP_GetUsages"));
      mHidP_GetUsageValue = reinterpret_cast<decltype(HidP_GetUsageValue)*>(
          GetProcAddress(mModule, "HidP_GetUsageValue"));
      mHidP_GetScaledUsageValue =
          reinterpret_cast<decltype(HidP_GetScaledUsageValue)*>(
              GetProcAddress(mModule, "HidP_GetScaledUsageValue"));
    }
  }

  ~HIDLoader() {
    if (mModule) {
      FreeLibrary(mModule);
    }
  }

  explicit operator bool() {
    return mModule && mHidD_GetProductString && mHidP_GetCaps &&
           mHidP_GetButtonCaps && mHidP_GetValueCaps && mHidP_GetUsages &&
           mHidP_GetUsageValue && mHidP_GetScaledUsageValue;
  }

  decltype(HidD_GetProductString)* mHidD_GetProductString;
  decltype(HidP_GetCaps)* mHidP_GetCaps;
  decltype(HidP_GetButtonCaps)* mHidP_GetButtonCaps;
  decltype(HidP_GetValueCaps)* mHidP_GetValueCaps;
  decltype(HidP_GetUsages)* mHidP_GetUsages;
  decltype(HidP_GetUsageValue)* mHidP_GetUsageValue;
  decltype(HidP_GetScaledUsageValue)* mHidP_GetScaledUsageValue;

 private:
  HMODULE mModule;
};

HWND sHWnd = nullptr;

static void DirectInputMessageLoopOnceCallback(nsITimer* aTimer,
                                               void* aClosure) {
  MOZ_ASSERT(NS_GetCurrentThread() == gMonitorThread);
  MSG msg;
  while (PeekMessageW(&msg, sHWnd, 0, 0, PM_REMOVE) > 0) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  aTimer->Cancel();
  if (!sIsShutdown) {
    aTimer->InitWithNamedFuncCallback(DirectInputMessageLoopOnceCallback,
                                      nullptr, kWindowsGamepadPollInterval,
                                      nsITimer::TYPE_ONE_SHOT,
                                      "DirectInputMessageLoopOnceCallback");
  }
}

class WindowsGamepadService {
 public:
  WindowsGamepadService() {
    mDirectInputTimer = NS_NewTimer();
    mXInputTimer = NS_NewTimer();
    mDeviceChangeTimer = NS_NewTimer();
  }
  virtual ~WindowsGamepadService() { Cleanup(); }

  void DevicesChanged(bool aIsStablizing);

  void StartMessageLoop() {
    MOZ_ASSERT(mDirectInputTimer);
    mDirectInputTimer->InitWithNamedFuncCallback(
        DirectInputMessageLoopOnceCallback, nullptr,
        kWindowsGamepadPollInterval, nsITimer::TYPE_ONE_SHOT,
        "DirectInputMessageLoopOnceCallback");
  }

  void Startup();
  void Shutdown();
  // Parse gamepad input from a WM_INPUT message.
  bool HandleRawInput(HRAWINPUT handle);
  void SetLightIndicatorColor(const Tainted<GamepadHandle>& aGamepadHandle,
                              const Tainted<uint32_t>& aLightColorIndex,
                              const uint8_t& aRed, const uint8_t& aGreen,
                              const uint8_t& aBlue);
  size_t WriteOutputReport(const std::vector<uint8_t>& aReport);
  static void XInputMessageLoopOnceCallback(nsITimer* aTimer, void* aClosure);
  static void DevicesChangeCallback(nsITimer* aTimer, void* aService);

 private:
  void ScanForDevices();
  // Look for connected raw input devices.
  void ScanForRawInputDevices();
  // Look for connected XInput devices.
  bool ScanForXInputDevices();
  bool HaveXInputGamepad(unsigned int userIndex);

  bool mIsXInputMonitoring;
  void PollXInput();
  void CheckXInputChanges(Gamepad& gamepad, XINPUT_STATE& state);

  // Get information about a raw input gamepad.
  bool GetRawGamepad(HANDLE handle);
  void Cleanup();

  // List of connected devices.
  nsTArray<Gamepad> mGamepads;

  HIDLoader mHID;
  nsAutoHandle mHidHandle;
  XInputLoader mXInput;

  nsCOMPtr<nsITimer> mDirectInputTimer;
  nsCOMPtr<nsITimer> mXInputTimer;
  nsCOMPtr<nsITimer> mDeviceChangeTimer;
};

void WindowsGamepadService::ScanForRawInputDevices() {
  if (!mHID) {
    return;
  }

  UINT numDevices;
  if (GetRawInputDeviceList(nullptr, &numDevices, sizeof(RAWINPUTDEVICELIST)) ==
      kRawInputError) {
    return;
  }
  nsTArray<RAWINPUTDEVICELIST> devices(numDevices);
  devices.SetLength(numDevices);
  if (GetRawInputDeviceList(devices.Elements(), &numDevices,
                            sizeof(RAWINPUTDEVICELIST)) == kRawInputError) {
    return;
  }

  for (unsigned i = 0; i < devices.Length(); i++) {
    if (devices[i].dwType == RIM_TYPEHID) {
      GetRawGamepad(devices[i].hDevice);
    }
  }
}

// static
void WindowsGamepadService::XInputMessageLoopOnceCallback(nsITimer* aTimer,
                                                          void* aService) {
  MOZ_ASSERT(aService);
  WindowsGamepadService* self = static_cast<WindowsGamepadService*>(aService);
  self->PollXInput();
  if (self->mIsXInputMonitoring) {
    aTimer->Cancel();
    aTimer->InitWithNamedFuncCallback(
        XInputMessageLoopOnceCallback, self, kWindowsGamepadPollInterval,
        nsITimer::TYPE_ONE_SHOT, "XInputMessageLoopOnceCallback");
  }
}

// static
void WindowsGamepadService::DevicesChangeCallback(nsITimer* aTimer,
                                                  void* aService) {
  MOZ_ASSERT(aService);
  WindowsGamepadService* self = static_cast<WindowsGamepadService*>(aService);
  self->DevicesChanged(false);
}

bool WindowsGamepadService::HaveXInputGamepad(unsigned int userIndex) {
  for (unsigned int i = 0; i < mGamepads.Length(); i++) {
    if (mGamepads[i].type == kXInputGamepad &&
        mGamepads[i].userIndex == userIndex) {
      mGamepads[i].present = true;
      return true;
    }
  }
  return false;
}

bool WindowsGamepadService::ScanForXInputDevices() {
  MOZ_ASSERT(mXInput, "XInput should be present!");

  bool found = false;
  RefPtr<GamepadPlatformService> service =
      GamepadPlatformService::GetParentService();
  if (!service) {
    return found;
  }

  for (unsigned int i = 0; i < XUSER_MAX_COUNT; i++) {
    XINPUT_STATE state = {};

    if (!mXInput.mXInputGetState ||
        mXInput.mXInputGetState(i, &state) != ERROR_SUCCESS) {
      continue;
    }

    found = true;
    // See if this device is already present in our list.
    if (HaveXInputGamepad(i)) {
      continue;
    }

    // Not already present, add it.
    Gamepad gamepad(kStandardGamepadAxes, kStandardGamepadButtons,
                    kXInputGamepad);
    gamepad.userIndex = i;
    gamepad.state = state;
    gamepad.gamepadHandle = service->AddGamepad(
        "xinput", GamepadMappingType::Standard, GamepadHand::_empty,
        kStandardGamepadButtons, kStandardGamepadAxes, 0, 0,
        0);  // TODO: Bug 680289, implement gamepad haptics for Windows.
    mGamepads.AppendElement(std::move(gamepad));
  }

  return found;
}

void WindowsGamepadService::ScanForDevices() {
  RefPtr<GamepadPlatformService> service =
      GamepadPlatformService::GetParentService();
  if (!service) {
    return;
  }

  for (int i = mGamepads.Length() - 1; i >= 0; i--) {
    mGamepads[i].present = false;
  }

  if (mHID) {
    ScanForRawInputDevices();
  }
  if (mXInput) {
    mXInputTimer->Cancel();
    if (ScanForXInputDevices()) {
      mIsXInputMonitoring = true;
      mXInputTimer->InitWithNamedFuncCallback(
          XInputMessageLoopOnceCallback, this, kWindowsGamepadPollInterval,
          nsITimer::TYPE_ONE_SHOT, "XInputMessageLoopOnceCallback");
    } else {
      mIsXInputMonitoring = false;
    }
  }

  // Look for devices that are no longer present and remove them.
  for (int i = mGamepads.Length() - 1; i >= 0; i--) {
    if (!mGamepads[i].present) {
      service->RemoveGamepad(mGamepads[i].gamepadHandle);
      mGamepads.RemoveElementAt(i);
    }
  }
}

void WindowsGamepadService::PollXInput() {
  for (unsigned int i = 0; i < mGamepads.Length(); i++) {
    if (mGamepads[i].type != kXInputGamepad) {
      continue;
    }

    XINPUT_STATE state = {};

    if (!mXInput.mXInputGetState ||
        mXInput.mXInputGetState(i, &state) != ERROR_SUCCESS) {
      continue;
    }

    if (state.dwPacketNumber != mGamepads[i].state.dwPacketNumber) {
      CheckXInputChanges(mGamepads[i], state);
    }
  }
}

void WindowsGamepadService::CheckXInputChanges(Gamepad& gamepad,
                                               XINPUT_STATE& state) {
  RefPtr<GamepadPlatformService> service =
      GamepadPlatformService::GetParentService();
  if (!service) {
    return;
  }
  // Handle digital buttons first
  for (size_t b = 0; b < kNumMappings; b++) {
    if (state.Gamepad.wButtons & kXIButtonMap[b].button &&
        !(gamepad.state.Gamepad.wButtons & kXIButtonMap[b].button)) {
      // Button pressed
      service->NewButtonEvent(gamepad.gamepadHandle, kXIButtonMap[b].mapped,
                              true);
    } else if (!(state.Gamepad.wButtons & kXIButtonMap[b].button) &&
               gamepad.state.Gamepad.wButtons & kXIButtonMap[b].button) {
      // Button released
      service->NewButtonEvent(gamepad.gamepadHandle, kXIButtonMap[b].mapped,
                              false);
    }
  }

  // Then triggers
  if (state.Gamepad.bLeftTrigger != gamepad.state.Gamepad.bLeftTrigger) {
    const bool pressed =
        state.Gamepad.bLeftTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
    service->NewButtonEvent(gamepad.gamepadHandle, kButtonLeftTrigger, pressed,
                            state.Gamepad.bLeftTrigger / 255.0);
  }
  if (state.Gamepad.bRightTrigger != gamepad.state.Gamepad.bRightTrigger) {
    const bool pressed =
        state.Gamepad.bRightTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
    service->NewButtonEvent(gamepad.gamepadHandle, kButtonRightTrigger, pressed,
                            state.Gamepad.bRightTrigger / 255.0);
  }

  // Finally deal with analog sticks
  // TODO: bug 1001955 - Support deadzones.
  if (state.Gamepad.sThumbLX != gamepad.state.Gamepad.sThumbLX) {
    const float div = state.Gamepad.sThumbLX > 0 ? 32767.0 : 32768.0;
    service->NewAxisMoveEvent(gamepad.gamepadHandle, kLeftStickXAxis,
                              state.Gamepad.sThumbLX / div);
  }
  if (state.Gamepad.sThumbLY != gamepad.state.Gamepad.sThumbLY) {
    const float div = state.Gamepad.sThumbLY > 0 ? 32767.0 : 32768.0;
    service->NewAxisMoveEvent(gamepad.gamepadHandle, kLeftStickYAxis,
                              -1.0 * state.Gamepad.sThumbLY / div);
  }
  if (state.Gamepad.sThumbRX != gamepad.state.Gamepad.sThumbRX) {
    const float div = state.Gamepad.sThumbRX > 0 ? 32767.0 : 32768.0;
    service->NewAxisMoveEvent(gamepad.gamepadHandle, kRightStickXAxis,
                              state.Gamepad.sThumbRX / div);
  }
  if (state.Gamepad.sThumbRY != gamepad.state.Gamepad.sThumbRY) {
    const float div = state.Gamepad.sThumbRY > 0 ? 32767.0 : 32768.0;
    service->NewAxisMoveEvent(gamepad.gamepadHandle, kRightStickYAxis,
                              -1.0 * state.Gamepad.sThumbRY / div);
  }
  gamepad.state = state;
}

// Used to sort a list of axes by HID usage.
class HidValueComparator {
 public:
  bool Equals(const Gamepad::axisValue& c1,
              const Gamepad::axisValue& c2) const {
    return c1.caps.UsagePage == c2.caps.UsagePage &&
           c1.caps.Range.UsageMin == c2.caps.Range.UsageMin;
  }
  bool LessThan(const Gamepad::axisValue& c1,
                const Gamepad::axisValue& c2) const {
    if (c1.caps.UsagePage == c2.caps.UsagePage) {
      return c1.caps.Range.UsageMin < c2.caps.Range.UsageMin;
    }
    return c1.caps.UsagePage < c2.caps.UsagePage;
  }
};

// GetRawGamepad() processes its raw data from HID and
// then trying to remapping buttons and axes based on
// the mapping rules that are defined for different gamepad products.
bool WindowsGamepadService::GetRawGamepad(HANDLE handle) {
  RefPtr<GamepadPlatformService> service =
      GamepadPlatformService::GetParentService();
  if (!service) {
    return true;
  }

  if (!mHID) {
    return false;
  }

  for (unsigned i = 0; i < mGamepads.Length(); i++) {
    if (mGamepads[i].type == kRawInputGamepad &&
        mGamepads[i].handle == handle) {
      mGamepads[i].present = true;
      return true;
    }
  }

  RID_DEVICE_INFO rdi = {};
  UINT size = rdi.cbSize = sizeof(RID_DEVICE_INFO);
  if (GetRawInputDeviceInfo(handle, RIDI_DEVICEINFO, &rdi, &size) ==
      kRawInputError) {
    return false;
  }
  // Ensure that this is a device we care about
  if (!SupportedUsage(rdi.hid.usUsagePage, rdi.hid.usUsage)) {
    return false;
  }

  // Device name is a mostly-opaque string.
  if (GetRawInputDeviceInfo(handle, RIDI_DEVICENAME, nullptr, &size) ==
      kRawInputError) {
    return false;
  }

  nsTArray<wchar_t> devname(size);
  devname.SetLength(size);
  if (GetRawInputDeviceInfo(handle, RIDI_DEVICENAME, devname.Elements(),
                            &size) == kRawInputError) {
    return false;
  }

  // Per http://msdn.microsoft.com/en-us/library/windows/desktop/ee417014.aspx
  // device names containing "IG_" are XInput controllers. Ignore those
  // devices since we'll handle them with XInput.
  if (wcsstr(devname.Elements(), L"IG_")) {
    return false;
  }

  // Product string is a human-readable name.
  // Per
  // http://msdn.microsoft.com/en-us/library/windows/hardware/ff539681%28v=vs.85%29.aspx
  // "For USB devices, the maximum string length is 126 wide characters (not
  // including the terminating NULL character)."
  wchar_t name[128] = {0};
  size = sizeof(name);
  nsTArray<char> gamepad_name;
  // Creating this file with FILE_FLAG_OVERLAPPED to perform
  // an asynchronous request in WriteOutputReport.
  mHidHandle.own(CreateFile(devname.Elements(), GENERIC_READ | GENERIC_WRITE,
                            FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
                            OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr));
  if (mHidHandle != INVALID_HANDLE_VALUE) {
    if (mHID.mHidD_GetProductString(mHidHandle, &name, size)) {
      int bytes = WideCharToMultiByte(CP_UTF8, 0, name, -1, nullptr, 0, nullptr,
                                      nullptr);
      gamepad_name.SetLength(bytes);
      WideCharToMultiByte(CP_UTF8, 0, name, -1, gamepad_name.Elements(), bytes,
                          nullptr, nullptr);
    }
  }
  if (gamepad_name.Length() == 0 || !gamepad_name[0]) {
    const char kUnknown[] = "Unknown Gamepad";
    gamepad_name.SetLength(std::size(kUnknown));
    strcpy_s(gamepad_name.Elements(), gamepad_name.Length(), kUnknown);
  }

  char gamepad_id[256] = {0};
  _snprintf_s(gamepad_id, _TRUNCATE, "%04x-%04x-%s", rdi.hid.dwVendorId,
              rdi.hid.dwProductId, gamepad_name.Elements());

  nsTArray<uint8_t> preparsedbytes;
  if (!GetPreparsedData(handle, preparsedbytes)) {
    return false;
  }

  PHIDP_PREPARSED_DATA parsed =
      reinterpret_cast<PHIDP_PREPARSED_DATA>(preparsedbytes.Elements());
  HIDP_CAPS caps;
  if (mHID.mHidP_GetCaps(parsed, &caps) != HIDP_STATUS_SUCCESS) {
    return false;
  }

  // Enumerate buttons.
  USHORT count = caps.NumberInputButtonCaps;
  nsTArray<HIDP_BUTTON_CAPS> buttonCaps(count);
  buttonCaps.SetLength(count);
  if (mHID.mHidP_GetButtonCaps(HidP_Input, buttonCaps.Elements(), &count,
                               parsed) != HIDP_STATUS_SUCCESS) {
    return false;
  }
  uint32_t numButtons = 0;
  for (unsigned i = 0; i < count; i++) {
    // Each buttonCaps is typically a range of buttons.
    numButtons +=
        buttonCaps[i].Range.UsageMax - buttonCaps[i].Range.UsageMin + 1;
  }

  // Enumerate value caps, which represent axes and d-pads.
  count = caps.NumberInputValueCaps;
  nsTArray<HIDP_VALUE_CAPS> axisCaps(count);
  axisCaps.SetLength(count);
  if (mHID.mHidP_GetValueCaps(HidP_Input, axisCaps.Elements(), &count,
                              parsed) != HIDP_STATUS_SUCCESS) {
    return false;
  }

  size_t numAxes = 0;
  nsTArray<Gamepad::axisValue> axes(kAxesLengthCap);
  // We store these value caps and handle the dpad info in GamepadRemapper
  // later.
  axes.SetLength(kAxesLengthCap);

  // Looking for the exisiting ramapping rule.
  bool defaultRemapper = false;
  RefPtr<GamepadRemapper> remapper = GetGamepadRemapper(
      rdi.hid.dwVendorId, rdi.hid.dwProductId, defaultRemapper);
  MOZ_ASSERT(remapper);

  for (size_t i = 0; i < count; i++) {
    const size_t axisIndex =
        axisCaps[i].Range.UsageMin - kAxisMinimumUsageNumber;
    if (axisIndex < kAxesLengthCap && !axes[axisIndex].active) {
      axes[axisIndex].caps = axisCaps[i];
      axes[axisIndex].active = true;
      numAxes = std::max(numAxes, axisIndex + 1);
    }
  }

  // Not already present, add it.

  remapper->SetAxisCount(numAxes);
  remapper->SetButtonCount(numButtons);
  Gamepad gamepad(numAxes, numButtons, kRawInputGamepad);
  gamepad.handle = handle;

  for (unsigned i = 0; i < gamepad.numAxes; i++) {
    gamepad.axes[i] = axes[i];
  }

  gamepad.remapper = remapper.forget();
  // TODO: Bug 680289, implement gamepad haptics for Windows.
  gamepad.gamepadHandle = service->AddGamepad(
      gamepad_id, gamepad.remapper->GetMappingType(), GamepadHand::_empty,
      gamepad.remapper->GetButtonCount(), gamepad.remapper->GetAxisCount(), 0,
      gamepad.remapper->GetLightIndicatorCount(),
      gamepad.remapper->GetTouchEventCount());

  nsTArray<GamepadLightIndicatorType> lightTypes;
  gamepad.remapper->GetLightIndicators(lightTypes);
  for (uint32_t i = 0; i < lightTypes.Length(); ++i) {
    if (lightTypes[i] != GamepadLightIndicator::DefaultType()) {
      service->NewLightIndicatorTypeEvent(gamepad.gamepadHandle, i,
                                          lightTypes[i]);
    }
  }

  mGamepads.AppendElement(std::move(gamepad));
  return true;
}

bool WindowsGamepadService::HandleRawInput(HRAWINPUT handle) {
  if (!mHID) {
    return false;
  }

  RefPtr<GamepadPlatformService> service =
      GamepadPlatformService::GetParentService();
  if (!service) {
    return false;
  }

  // First, get data from the handle
  UINT size;
  GetRawInputData(handle, RID_INPUT, nullptr, &size, sizeof(RAWINPUTHEADER));
  nsTArray<uint8_t> data(size);
  data.SetLength(size);
  if (GetRawInputData(handle, RID_INPUT, data.Elements(), &size,
                      sizeof(RAWINPUTHEADER)) == kRawInputError) {
    return false;
  }
  PRAWINPUT raw = reinterpret_cast<PRAWINPUT>(data.Elements());

  Gamepad* gamepad = nullptr;
  for (unsigned i = 0; i < mGamepads.Length(); i++) {
    if (mGamepads[i].type == kRawInputGamepad &&
        mGamepads[i].handle == raw->header.hDevice) {
      gamepad = &mGamepads[i];
      break;
    }
  }
  if (gamepad == nullptr) {
    return false;
  }

  // Second, get the preparsed data
  nsTArray<uint8_t> parsedbytes;
  if (!GetPreparsedData(raw->header.hDevice, parsedbytes)) {
    return false;
  }
  PHIDP_PREPARSED_DATA parsed =
      reinterpret_cast<PHIDP_PREPARSED_DATA>(parsedbytes.Elements());

  // Get all the pressed buttons.
  nsTArray<USAGE> usages(gamepad->numButtons);
  usages.SetLength(gamepad->numButtons);
  ULONG usageLength = gamepad->numButtons;
  if (mHID.mHidP_GetUsages(HidP_Input, kButtonUsagePage, 0, usages.Elements(),
                           &usageLength, parsed, (PCHAR)raw->data.hid.bRawData,
                           raw->data.hid.dwSizeHid) != HIDP_STATUS_SUCCESS) {
    return false;
  }

  nsTArray<bool> buttons(gamepad->numButtons);
  buttons.SetLength(gamepad->numButtons);
  // If we don't zero out the buttons array first, sometimes it can reuse
  // values.
  memset(buttons.Elements(), 0, gamepad->numButtons * sizeof(bool));

  for (unsigned i = 0; i < usageLength; i++) {
    // The button index in usages may be larger than what we detected when
    // enumerating gamepads. If so, warn and continue.
    //
    // Usage ID of 0 is reserved, so it should always be 1 or higher.
    if (NS_WARN_IF((usages[i] - 1u) >= buttons.Length())) {
      continue;
    }
    buttons[usages[i] - 1u] = true;
  }

  for (unsigned i = 0; i < gamepad->numButtons; i++) {
    if (gamepad->buttons[i] != buttons[i]) {
      gamepad->remapper->RemapButtonEvent(gamepad->gamepadHandle, i,
                                          buttons[i]);
      gamepad->buttons[i] = buttons[i];
    }
  }

  // Get all axis values.
  for (unsigned i = 0; i < gamepad->numAxes; i++) {
    double new_value;
    if (gamepad->axes[i].caps.LogicalMin < 0) {
      LONG value;
      if (mHID.mHidP_GetScaledUsageValue(
              HidP_Input, gamepad->axes[i].caps.UsagePage, 0,
              gamepad->axes[i].caps.Range.UsageMin, &value, parsed,
              (PCHAR)raw->data.hid.bRawData,
              raw->data.hid.dwSizeHid) != HIDP_STATUS_SUCCESS) {
        continue;
      }
      new_value = ScaleAxis(value, gamepad->axes[i].caps.LogicalMin,
                            gamepad->axes[i].caps.LogicalMax);
    } else {
      ULONG value;
      if (mHID.mHidP_GetUsageValue(
              HidP_Input, gamepad->axes[i].caps.UsagePage, 0,
              gamepad->axes[i].caps.Range.UsageMin, &value, parsed,
              (PCHAR)raw->data.hid.bRawData,
              raw->data.hid.dwSizeHid) != HIDP_STATUS_SUCCESS) {
        continue;
      }

      new_value = ScaleAxis(value, gamepad->axes[i].caps.LogicalMin,
                            gamepad->axes[i].caps.LogicalMax);
    }
    if (gamepad->axes[i].value != new_value) {
      gamepad->remapper->RemapAxisMoveEvent(gamepad->gamepadHandle, i,
                                            new_value);
      gamepad->axes[i].value = new_value;
    }
  }

  BYTE* rawData = raw->data.hid.bRawData;
  gamepad->remapper->ProcessTouchData(gamepad->gamepadHandle, rawData);

  return true;
}

void WindowsGamepadService::SetLightIndicatorColor(
    const Tainted<GamepadHandle>& aGamepadHandle,
    const Tainted<uint32_t>& aLightColorIndex, const uint8_t& aRed,
    const uint8_t& aGreen, const uint8_t& aBlue) {
  // We get aControllerIdx from GamepadPlatformService::AddGamepad(),
  // It begins from 1 and is stored at Gamepad.id.
  const Gamepad* gamepad = (MOZ_FIND_AND_VALIDATE(
      aGamepadHandle, list_item.gamepadHandle == aGamepadHandle, mGamepads));
  if (!gamepad) {
    MOZ_ASSERT(false);
    return;
  }

  RefPtr<GamepadRemapper> remapper = gamepad->remapper;
  if (!remapper ||
      MOZ_IS_VALID(aLightColorIndex,
                   remapper->GetLightIndicatorCount() <= aLightColorIndex)) {
    MOZ_ASSERT(false);
    return;
  }

  std::vector<uint8_t> report;
  remapper->GetLightColorReport(aRed, aGreen, aBlue, report);
  WriteOutputReport(report);
}

size_t WindowsGamepadService::WriteOutputReport(
    const std::vector<uint8_t>& aReport) {
  DCHECK(static_cast<const void*>(aReport.data()));
  DCHECK_GE(aReport.size(), 1U);
  if (!mHidHandle) return 0;

  nsAutoHandle eventHandle(::CreateEvent(nullptr, FALSEFALSE, nullptr));
  OVERLAPPED overlapped = {0};
  overlapped.hEvent = eventHandle;

  // Doing an asynchronous write to allows us to time out
  // if the write takes too long.
  DWORD bytesWritten = 0;
  BOOL writeSuccess =
      ::WriteFile(mHidHandle, static_cast<const void*>(aReport.data()),
                  aReport.size(), &bytesWritten, &overlapped);
  if (!writeSuccess) {
    DWORD error = ::GetLastError();
    if (error == ERROR_IO_PENDING) {
      // Wait for the write to complete. This causes WriteOutputReport to behave
      // synchronously but with a timeout.
      DWORD wait_object = ::WaitForSingleObject(overlapped.hEvent, 100);
      if (wait_object == WAIT_OBJECT_0) {
        if (!::GetOverlappedResult(mHidHandle, &overlapped, &bytesWritten,
                                   TRUE)) {
          return 0;
        }
      } else {
        // Wait failed, or the timeout was exceeded before the write completed.
        // Cancel the write request.
        if (::CancelIo(mHidHandle)) {
          wait_object = ::WaitForSingleObject(overlapped.hEvent, INFINITE);
          MOZ_ASSERT(wait_object == WAIT_OBJECT_0);
        }
      }
    }
  }
  return writeSuccess ? bytesWritten : 0;
}

void WindowsGamepadService::Startup() { ScanForDevices(); }

void WindowsGamepadService::Shutdown() { Cleanup(); }

void WindowsGamepadService::Cleanup() {
  mIsXInputMonitoring = false;
  if (mDirectInputTimer) {
    mDirectInputTimer->Cancel();
  }
  if (mXInputTimer) {
    mXInputTimer->Cancel();
  }
  if (mDeviceChangeTimer) {
    mDeviceChangeTimer->Cancel();
  }

  mGamepads.Clear();
}

void WindowsGamepadService::DevicesChanged(bool aIsStablizing) {
  if (aIsStablizing) {
    mDeviceChangeTimer->Cancel();
    mDeviceChangeTimer->InitWithNamedFuncCallback(
        DevicesChangeCallback, this, kDevicesChangedStableDelay,
        nsITimer::TYPE_ONE_SHOT, "DevicesChangeCallback");
  } else {
    ScanForDevices();
  }
}

bool RegisterRawInput(HWND hwnd, bool enable) {
  nsTArray<RAWINPUTDEVICE> rid(std::size(kUsagePages));
  rid.SetLength(std::size(kUsagePages));

  for (unsigned i = 0; i < rid.Length(); i++) {
    rid[i].usUsagePage = kUsagePages[i].usagePage;
    rid[i].usUsage = kUsagePages[i].usage;
    rid[i].dwFlags =
        enable ? RIDEV_EXINPUTSINK | RIDEV_DEVNOTIFY : RIDEV_REMOVE;
    rid[i].hwndTarget = hwnd;
  }

  if (!RegisterRawInputDevices(rid.Elements(), rid.Length(),
                               sizeof(RAWINPUTDEVICE))) {
    return false;
  }
  return true;
}

static LRESULT CALLBACK GamepadWindowProc(HWND hwnd, UINT msg, WPARAM wParam,
                                          LPARAM lParam) {
  const unsigned int DBT_DEVICEARRIVAL = 0x8000;
  const unsigned int DBT_DEVICEREMOVECOMPLETE = 0x8004;
  const unsigned int DBT_DEVNODES_CHANGED = 0x7;

  switch (msg) {
    case WM_DEVICECHANGE:
      if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE ||
          wParam == DBT_DEVNODES_CHANGED) {
        if (gService) {
          gService->DevicesChanged(true);
        }
      }
      break;
    case WM_INPUT:
      if (gService) {
        gService->HandleRawInput(reinterpret_cast<HRAWINPUT>(lParam));
      }
      break;
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

class StartWindowsGamepadServiceRunnable final : public Runnable {
 public:
  StartWindowsGamepadServiceRunnable()
      : Runnable("StartWindowsGamepadServiceRunnable") {}

  NS_IMETHOD Run() override {
    MOZ_ASSERT(NS_GetCurrentThread() == gMonitorThread);
    gService = new WindowsGamepadService();
    gService->Startup();

    if (sHWnd == nullptr) {
      WNDCLASSW wc;
      HMODULE hSelf = GetModuleHandle(nullptr);

      if (!GetClassInfoW(hSelf, L"MozillaGamepadClass", &wc)) {
        ZeroMemory(&wc, sizeof(WNDCLASSW));
        wc.hInstance = hSelf;
        wc.lpfnWndProc = GamepadWindowProc;
        wc.lpszClassName = L"MozillaGamepadClass";
        RegisterClassW(&wc);
      }

      sHWnd = CreateWindowW(L"MozillaGamepadClass", L"Gamepad Watcher", 0, 0, 0,
                            0, 0, nullptr, nullptr, hSelf, nullptr);
      RegisterRawInput(sHWnd, true);
    }

    // Explicitly start the message loop
    gService->StartMessageLoop();

    return NS_OK;
  }

 private:
  ~StartWindowsGamepadServiceRunnable() {}
};

class StopWindowsGamepadServiceRunnable final : public Runnable {
 public:
  StopWindowsGamepadServiceRunnable()
      : Runnable("StopWindowsGamepadServiceRunnable") {}

  NS_IMETHOD Run() override {
    MOZ_ASSERT(NS_GetCurrentThread() == gMonitorThread);
    if (sHWnd) {
      RegisterRawInput(sHWnd, false);
      DestroyWindow(sHWnd);
      sHWnd = nullptr;
    }

    gService->Shutdown();
    delete gService;
    gService = nullptr;

    return NS_OK;
  }

 private:
  ~StopWindowsGamepadServiceRunnable() {}
};

}  // namespace

namespace mozilla::dom {

using namespace mozilla::ipc;

void StartGamepadMonitoring() {
  AssertIsOnBackgroundThread();

  if (gMonitorThread || gService) {
    return;
  }
  sIsShutdown = false;
  NS_NewNamedThread("Gamepad", getter_AddRefs(gMonitorThread));
  gMonitorThread->Dispatch(new StartWindowsGamepadServiceRunnable(),
                           NS_DISPATCH_NORMAL);
}

void StopGamepadMonitoring() {
  AssertIsOnBackgroundThread();

  if (sIsShutdown) {
    return;
  }
  sIsShutdown = true;
  gMonitorThread->Dispatch(new StopWindowsGamepadServiceRunnable(),
                           NS_DISPATCH_NORMAL);
  gMonitorThread->Shutdown();
  gMonitorThread = nullptr;
}

void SetGamepadLightIndicatorColor(const Tainted<GamepadHandle>& aGamepadHandle,
                                   const Tainted<uint32_t>& aLightColorIndex,
                                   const uint8_t& aRed, const uint8_t& aGreen,
                                   const uint8_t& aBlue) {
  MOZ_ASSERT(gService);
  if (!gService) {
    return;
  }
  gService->SetLightIndicatorColor(aGamepadHandle, aLightColorIndex, aRed,
                                   aGreen, aBlue);
}

}  // namespace mozilla::dom

Messung V0.5
C=92 H=92 G=91

¤ Dauer der Verarbeitung: 0.15 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.