How use HID compatible gamepad in mediaportal (1 Viewer)

Stéphane Lenclud

Retired Team Member
  • Premium Supporter
  • April 29, 2013
    2,576
    1,294
    Home Country
    Germany Germany
    Actually I have a Logitech Cordless RumblePad 2 here and I'm pretty sure it needs:
    UsagePage="GenericDesktopControls"
    UsageCollection="GamePad"

    Yours should be the same. From here on you should be able to merge in the buttons mapping from DirectInput.xml.
    You'll just have to check that the codes are matching the once sent by your device by looking in MP logs.
     

    Stéphane Lenclud

    Retired Team Member
  • Premium Supporter
  • April 29, 2013
    2,576
    1,294
    Home Country
    Germany Germany
    I did try it myself and I the buttons code I could get, they are basically the button ID from 1 to 12 in my case.
    However the joystick and sadly even the direction pad would need some specific handling.
    So as it stands you should be able to map your basic buttons and even use them for direction actions and thus properly control MP.
    However the direction pad won't work so it would be rather unintuitive to use.

    More coding needed for proper HID gamepad support I'm afraid. Possibly for MP 1.14, end of 2015.
     

    Stéphane Lenclud

    Retired Team Member
  • Premium Supporter
  • April 29, 2013
    2,576
    1,294
    Home Country
    Germany Germany
    Just use this configuration to control MP with the buttons on your gamepad.
    D-pad and Joysticks won't work.
    Have fun!
     

    Attachments

    • Generic-HID.xml
      16.5 KB

    Stéphane Lenclud

    Retired Team Member
  • Premium Supporter
  • April 29, 2013
    2,576
    1,294
    Home Country
    Germany Germany
    Note for future implementation of proper gamepad support see example there:
    https://dxr.mozilla.org/mozilla-central/source/hal/windows/WindowsGamepad.cpp
    Now moved to:
    https://dxr.mozilla.org/mozilla-central/source/dom/gamepad/windows/WindowsGamepad.cpp

    Google search: https://www.google.de/search?q=HID+gamepad+handling+direction+pad

    Here is a copy of the code linked above
    /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
    /* 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 "nsIComponentManager.h"
    #include "nsIObserver.h"
    #include "nsIObserverService.h"
    #include "nsITimer.h"
    #include "nsTArray.h"
    #include "mozilla/ArrayUtils.h"
    #include "mozilla/dom/GamepadService.h"
    #include "mozilla/Services.h"
    namespace {
    using namespace mozilla::dom;
    using mozilla::ArrayLength;
    // USB HID usage tables, page 1 (Hat switch)
    const unsigned kUsageDpad = 0x39;
    // USB HID usage tables, page 1, 0x30 = X
    const unsigned kFirstAxis = 0x30;
    // USB HID usage tables
    const unsigned kDesktopUsagePage = 0x1;
    const unsigned kButtonUsagePage = 0x9;
    // Arbitrary. In practice 10 buttons/6 axes is the near maximum.
    const unsigned kMaxButtons = 32;
    const unsigned kMaxAxes = 32;
    // 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;
    // XInput is a purely polling-driven API, so we need to
    // poll it periodically. 50ms is arbitrarily chosen.
    const uint32_t kXInputPollInterval = 50;
    const UINT kRawInputError = (UINT)-1;
    #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_A, 0 },
    { XINPUT_GAMEPAD_B, 1 },
    { XINPUT_GAMEPAD_X, 2 },
    { XINPUT_GAMEPAD_Y, 3 }
    };
    const size_t kNumMappings = ArrayLength(kXIButtonMap);
    enum GamepadType {
    kNoGamepad = 0,
    kRawInputGamepad,
    kXInputGamepad
    };
    class WindowsGamepadService;
    WindowsGamepadService* gService = nullptr;
    struct Gamepad {
    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;
    // ID from the GamepadService, also used as the index into
    // WindowsGamepadService::mGamepads.
    int id;
    // Information about the physical device.
    unsigned numAxes;
    unsigned numButtons;
    bool hasDpad;
    HIDP_VALUE_CAPS dpadCaps;
    bool buttons[kMaxButtons];
    struct {
    HIDP_VALUE_CAPS caps;
    double value;
    } axes[kMaxAxes];
    // Used during rescan to find devices that were disconnected.
    bool present;
    };
    // Drop this in favor of decltype when we require a new enough SDK.
    typedef void (WINAPI *XInputEnable_func)(BOOL);
    // RAII class to wrap loading the XInput DLL
    class XInputLoader {
    public:
    XInputLoader() : module(nullptr),
    mXInputEnable(nullptr),
    mXInputGetState(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 = ArrayLength(dlls);
    for (size_t i = 0; i < kNumDLLs; ++i) {
    module = LoadLibraryW(dlls);
    if (module) {
    mXInputEnable = reinterpret_cast<XInputEnable_func>(
    GetProcAddress(module, "XInputEnable"));
    mXInputGetState = reinterpret_cast<decltype(XInputGetState)*>(
    GetProcAddress(module, "XInputGetState"));
    if (mXInputEnable) {
    mXInputEnable(TRUE);
    }
    break;
    }
    }
    }
    ~XInputLoader() {
    //mXInputEnable = nullptr;
    mXInputGetState = nullptr;
    if (module) {
    FreeLibrary(module);
    }
    }
    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;
    }
    /*
    * Given a value from a d-pad (POV hat in USB HID terminology),
    * represent it as 4 buttons, one for each cardinal direction.
    */
    void
    UnpackDpad(LONG dpad_value, const Gamepad* gamepad, bool buttons[kMaxButtons])
    {
    const unsigned kUp = gamepad->numButtons - 4;
    const unsigned kDown = gamepad->numButtons - 3;
    const unsigned kLeft = gamepad->numButtons - 2;
    const unsigned kRight = gamepad->numButtons - 1;
    // Different controllers have different ways of representing
    // "nothing is pressed", but they're all outside the range of values.
    if (dpad_value < gamepad->dpadCaps.LogicalMin
    || dpad_value > gamepad->dpadCaps.LogicalMax) {
    // Nothing is pressed.
    return;
    }
    // Normalize value to start at 0.
    int value = dpad_value - gamepad->dpadCaps.LogicalMin;
    // Value will be in the range 0-7. The value represents the
    // position of the d-pad around a circle, with 0 being straight up,
    // 2 being right, 4 being straight down, and 6 being left.
    if (value < 2 || value > 6) {
    buttons[kUp] = true;
    }
    if (value > 2 && value < 6) {
    buttons[kDown] = true;
    }
    if (value > 4) {
    buttons[kLeft] = true;
    }
    if (value > 0 && value < 4) {
    buttons[kRight] = true;
    }
    }
    /*
    * 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 < ArrayLength(kUsagePages); i++) {
    if (page == kUsagePages.usagePage && usage == kUsagePages.usage) {
    return true;
    }
    }
    return false;
    }
    class Observer : public nsIObserver {
    public:
    NS_DECL_ISUPPORTS
    NS_DECL_NSIOBSERVER
    Observer(WindowsGamepadService& svc) : mSvc(svc),
    mObserving(true)
    {
    nsresult rv;
    mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
    nsCOMPtr<nsIObserverService> observerService =
    mozilla::services::GetObserverService();
    observerService->AddObserver(this,
    NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID,
    false);
    }
    void Stop()
    {
    if (mTimer) {
    mTimer->Cancel();
    }
    if (mObserving) {
    nsCOMPtr<nsIObserverService> observerService =
    mozilla::services::GetObserverService();
    observerService->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
    mObserving = false;
    }
    }
    void SetDeviceChangeTimer()
    {
    // Set stable timer, since we will get multiple devices-changed
    // notifications at once
    if (mTimer) {
    mTimer->Cancel();
    mTimer->Init(this, kDevicesChangedStableDelay, nsITimer::TYPE_ONE_SHOT);
    }
    }
    private:
    virtual ~Observer()
    {
    Stop();
    }
    // Gamepad service owns us, we just hold a reference back to it.
    WindowsGamepadService& mSvc;
    nsCOMPtr<nsITimer> mTimer;
    bool mObserving;
    };
    NS_IMPL_ISUPPORTS(Observer, nsIObserver);
    class HIDLoader {
    public:
    HIDLoader() : mModule(LoadLibraryW(L"hid.dll")),
    mHidD_GetProductString(nullptr),
    mHidP_GetCaps(nullptr),
    mHidP_GetButtonCaps(nullptr),
    mHidP_GetValueCaps(nullptr),
    mHidP_GetUsages(nullptr),
    mHidP_GetUsageValue(nullptr),
    mHidP_GetScaledUsageValue(nullptr)
    {
    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);
    }
    }
    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;
    };
    class WindowsGamepadService {
    public:
    WindowsGamepadService();
    virtual ~WindowsGamepadService()
    {
    Cleanup();
    }
    enum DeviceChangeType {
    DeviceChangeNotification,
    DeviceChangeStable
    };
    void DevicesChanged(DeviceChangeType type);
    void Startup();
    void Shutdown();
    // Parse gamepad input from a WM_INPUT message.
    bool HandleRawInput(HRAWINPUT handle);
    private:
    void ScanForDevices();
    // Look for connected raw input devices.
    void ScanForRawInputDevices();
    // Look for connected XInput devices.
    bool ScanForXInputDevices();
    bool HaveXInputGamepad(int userIndex);
    // Timer callback for XInput polling
    static void XInputPollTimerCallback(nsITimer* aTimer, void* aClosure);
    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;
    nsRefPtr<Observer> mObserver;
    nsCOMPtr<nsITimer> mXInputPollTimer;
    HIDLoader mHID;
    XInputLoader mXInput;
    };
    WindowsGamepadService::WindowsGamepadService()
    {
    nsresult rv;
    mXInputPollTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
    mObserver = new Observer(*this);
    }
    void
    WindowsGamepadService::confused:canForRawInputDevices()
    {
    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.dwType == RIM_TYPEHID) {
    GetRawGamepad(devices.hDevice);
    }
    }
    }
    bool
    WindowsGamepadService::HaveXInputGamepad(int userIndex)
    {
    for (unsigned int i = 0; i < mGamepads.Length(); i++) {
    if (mGamepads.type == kXInputGamepad
    && mGamepads.userIndex == userIndex) {
    mGamepads.present = true;
    return true;
    }
    }
    return false;
    }
    bool
    WindowsGamepadService::confused:canForXInputDevices()
    {
    MOZ_ASSERT(mXInput, "XInput should be present!");
    nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
    if (!gamepadsvc) {
    return false;
    }
    bool found = false;
    for (int i = 0; i < XUSER_MAX_COUNT; i++) {
    XINPUT_STATE state = {};
    if (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 = {};
    gamepad.type = kXInputGamepad;
    gamepad.present = true;
    gamepad.state = state;
    gamepad.userIndex = i;
    gamepad.numButtons = kStandardGamepadButtons;
    gamepad.numAxes = kStandardGamepadAxes;
    gamepad.id = gamepadsvc->AddGamepad("xinput",
    GamepadMappingType::confused:tandard,
    kStandardGamepadButtons,
    kStandardGamepadAxes);
    mGamepads.AppendElement(gamepad);
    }
    return found;
    }
    void
    WindowsGamepadService::confused:canForDevices()
    {
    for (int i = mGamepads.Length() - 1; i >= 0; i--) {
    mGamepads.present = false;
    }
    if (mHID) {
    ScanForRawInputDevices();
    }
    if (mXInput) {
    mXInputPollTimer->Cancel();
    if (ScanForXInputDevices()) {
    mXInputPollTimer->InitWithFuncCallback(XInputPollTimerCallback,
    this,
    kXInputPollInterval,
    nsITimer::TYPE_REPEATING_SLACK);
    }
    }
    nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
    if (!gamepadsvc) {
    return;
    }
    // Look for devices that are no longer present and remove them.
    for (int i = mGamepads.Length() - 1; i >= 0; i--) {
    if (!mGamepads.present) {
    gamepadsvc->RemoveGamepad(mGamepads.id);
    mGamepads.RemoveElementAt(i);
    }
    }
    }
    // static
    void
    WindowsGamepadService::XInputPollTimerCallback(nsITimer* aTimer,
    void* aClosure)
    {
    WindowsGamepadService* self =
    reinterpret_cast<WindowsGamepadService*>(aClosure);
    self->PollXInput();
    }
    void
    WindowsGamepadService::pollXInput()
    {
    for (unsigned int i = 0; i < mGamepads.Length(); i++) {
    if (mGamepads.type != kXInputGamepad) {
    continue;
    }
    XINPUT_STATE state = {};
    DWORD res = mXInput.mXInputGetState(mGamepads.userIndex, &state);
    if (res == ERROR_SUCCESS
    && state.dwPacketNumber != mGamepads.state.dwPacketNumber) {
    CheckXInputChanges(mGamepads, state);
    }
    }
    }
    void WindowsGamepadService::CheckXInputChanges(Gamepad& gamepad,
    XINPUT_STATE& state) {
    nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
    // Handle digital buttons first
    for (size_t b = 0; b < kNumMappings; b++) {
    if (state.Gamepad.wButtons & kXIButtonMap.button &&
    !(gamepad.state.Gamepad.wButtons & kXIButtonMap.button)) {
    // Button pressed
    gamepadsvc->NewButtonEvent(gamepad.id, kXIButtonMap.mapped, true);
    } else if (!(state.Gamepad.wButtons & kXIButtonMap.button) &&
    gamepad.state.Gamepad.wButtons & kXIButtonMap.button) {
    // Button released
    gamepadsvc->NewButtonEvent(gamepad.id, kXIButtonMap.mapped, false);
    }
    }
    // Then triggers
    if (state.Gamepad.bLeftTrigger != gamepad.state.Gamepad.bLeftTrigger) {
    bool pressed =
    state.Gamepad.bLeftTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
    gamepadsvc->NewButtonEvent(gamepad.id, kButtonLeftTrigger,
    pressed, state.Gamepad.bLeftTrigger / 255.0);
    }
    if (state.Gamepad.bRightTrigger != gamepad.state.Gamepad.bRightTrigger) {
    bool pressed =
    state.Gamepad.bRightTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
    gamepadsvc->NewButtonEvent(gamepad.id, 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) {
    gamepadsvc->NewAxisMoveEvent(gamepad.id, kLeftStickXAxis,
    state.Gamepad.sThumbLX / 32767.0);
    }
    if (state.Gamepad.sThumbLY != gamepad.state.Gamepad.sThumbLY) {
    gamepadsvc->NewAxisMoveEvent(gamepad.id, kLeftStickYAxis,
    -1.0 * state.Gamepad.sThumbLY / 32767.0);
    }
    if (state.Gamepad.sThumbRX != gamepad.state.Gamepad.sThumbRX) {
    gamepadsvc->NewAxisMoveEvent(gamepad.id, kRightStickXAxis,
    state.Gamepad.sThumbRX / 32767.0);
    }
    if (state.Gamepad.sThumbRY != gamepad.state.Gamepad.sThumbRY) {
    gamepadsvc->NewAxisMoveEvent(gamepad.id, kRightStickYAxis,
    -1.0 * state.Gamepad.sThumbRY / 32767.0);
    }
    gamepad.state = state;
    }
    // Used to sort a list of axes by HID usage.
    class HidValueComparator {
    public:
    bool Equals(const HIDP_VALUE_CAPS& c1, const HIDP_VALUE_CAPS& c2) const
    {
    return c1.UsagePage == c2.UsagePage && c1.Range.UsageMin == c2.Range.UsageMin;
    }
    bool LessThan(const HIDP_VALUE_CAPS& c1, const HIDP_VALUE_CAPS& c2) const
    {
    if (c1.UsagePage == c2.UsagePage) {
    return c1.Range.UsageMin < c2.Range.UsageMin;
    }
    return c1.UsagePage < c2.UsagePage;
    }
    };
    bool
    WindowsGamepadService::GetRawGamepad(HANDLE handle)
    {
    if (!mHID) {
    return false;
    }
    for (unsigned i = 0; i < mGamepads.Length(); i++) {
    if (mGamepads.type == kRawInputGamepad && mGamepads.handle == handle) {
    mGamepads.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;
    }
    Gamepad gamepad = {};
    // 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 [URL]http://msdn.microsoft.com/en-us/library/windows/desktop/ee417014.aspx[/url]
    // 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 [URL]http://msdn.microsoft.com/en-us/library/windows/hardware/ff539681(v=vs.85).aspx[/url]
    // "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;
    HANDLE hid_handle = CreateFile(devname.Elements(), GENERIC_READ | GENERIC_WRITE,
    FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
    if (hid_handle) {
    if (mHID.mHidD_GetProductString(hid_handle, &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);
    }
    CloseHandle(hid_handle);
    }
    if (gamepad_name.Length() == 0 || !gamepad_name[0]) {
    const char kUnknown[] = "Unknown Gamepad";
    gamepad_name.SetLength(ArrayLength(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;
    }
    for (unsigned i = 0; i < count; i++) {
    // Each buttonCaps is typically a range of buttons.
    gamepad.numButtons +=
    buttonCaps.Range.UsageMax - buttonCaps.Range.UsageMin + 1;
    }
    gamepad.numButtons = std::min(gamepad.numButtons, kMaxButtons);
    // Enumerate value caps, which represent axes and d-pads.
    count = caps.NumberInputValueCaps;
    nsTArray<HIDP_VALUE_CAPS> valueCaps(count);
    valueCaps.SetLength(count);
    if (mHID.mHidP_GetValueCaps(HidP_Input, valueCaps.Elements(), &count, parsed)
    != HIDP_STATUS_SUCCESS) {
    return false;
    }
    nsTArray<HIDP_VALUE_CAPS> axes;
    // Sort the axes by usagePage and usage to expose a consistent ordering.
    HidValueComparator comparator;
    for (unsigned i = 0; i < count; i++) {
    if (valueCaps.UsagePage == kDesktopUsagePage
    && valueCaps.Range.UsageMin == kUsageDpad
    // Don't know how to handle d-pads that return weird values.
    && valueCaps.LogicalMax - valueCaps.LogicalMin == 7
    // Can't overflow buttons
    && gamepad.numButtons + 4 < kMaxButtons) {
    // d-pad gets special handling.
    // Ostensibly HID devices can expose multiple d-pads, but this
    // doesn't happen in practice.
    gamepad.hasDpad = true;
    gamepad.dpadCaps = valueCaps;
    // Expose d-pad as 4 additional buttons.
    gamepad.numButtons += 4;
    } else {
    axes.InsertElementSorted(valueCaps, comparator);
    }
    }
    gamepad.numAxes = std::min<size_t>(axes.Length(), kMaxAxes);
    for (unsigned i = 0; i < gamepad.numAxes; i++) {
    if (i >= kMaxAxes) {
    break;
    }
    gamepad.axes.caps = axes;
    }
    gamepad.type = kRawInputGamepad;
    gamepad.handle = handle;
    gamepad.present = true;
    nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
    if (!gamepadsvc) {
    return false;
    }
    gamepad.id = gamepadsvc->AddGamepad(gamepad_id,
    GamepadMappingType::_empty,
    gamepad.numButtons,
    gamepad.numAxes);
    mGamepads.AppendElement(gamepad);
    return true;
    }
    bool
    WindowsGamepadService::HandleRawInput(HRAWINPUT handle)
    {
    if (!mHID) {
    return false;
    }
    nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
    if (!gamepadsvc) {
    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.type == kRawInputGamepad
    && mGamepads.handle == raw->header.hDevice) {
    gamepad = &mGamepads;
    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;
    }
    bool buttons[kMaxButtons] = { false };
    usageLength = std::min<ULONG>(usageLength, kMaxButtons);
    for (unsigned i = 0; i < usageLength; i++) {
    buttons[usages - 1] = true;
    }
    if (gamepad->hasDpad) {
    // Get d-pad position as 4 buttons.
    ULONG value;
    if (mHID.mHidP_GetUsageValue(HidP_Input, gamepad->dpadCaps.UsagePage, 0, gamepad->dpadCaps.Range.UsageMin, &value, parsed, (PCHAR)raw->data.hid.bRawData, raw->data.hid.dwSizeHid) == HIDP_STATUS_SUCCESS) {
    UnpackDpad(static_cast<LONG>(value), gamepad, buttons);
    }
    }
    for (unsigned i = 0; i < gamepad->numButtons; i++) {
    if (gamepad->buttons != buttons) {
    gamepadsvc->NewButtonEvent(gamepad->id, i, buttons);
    gamepad->buttons = buttons;
    }
    }
    // Get all axis values.
    for (unsigned i = 0; i < gamepad->numAxes; i++) {
    double new_value;
    if (gamepad->axes.caps.LogicalMin < 0) {
    LONG value;
    if (mHID.mHidP_GetScaledUsageValue(HidP_Input, gamepad->axes.caps.UsagePage,
    0, gamepad->axes.caps.Range.UsageMin,
    &value, parsed,
    (PCHAR)raw->data.hid.bRawData,
    raw->data.hid.dwSizeHid)
    != HIDP_STATUS_SUCCESS) {
    continue;
    }
    new_value = ScaleAxis(value, gamepad->axes.caps.LogicalMin,
    gamepad->axes.caps.LogicalMax);
    }
    else {
    ULONG value;
    if (mHID.mHidP_GetUsageValue(HidP_Input, gamepad->axes.caps.UsagePage, 0,
    gamepad->axes.caps.Range.UsageMin, &value,
    parsed, (PCHAR)raw->data.hid.bRawData,
    raw->data.hid.dwSizeHid) != HIDP_STATUS_SUCCESS) {
    continue;
    }
    new_value = ScaleAxis(value, gamepad->axes.caps.LogicalMin,
    gamepad->axes.caps.LogicalMax);
    }
    if (gamepad->axes.value != new_value) {
    gamepadsvc->NewAxisMoveEvent(gamepad->id, i, new_value);
    gamepad->axes.value = new_value;
    }
    }
    return true;
    }
    void
    WindowsGamepadService::confused:tartup()
    {
    ScanForDevices();
    }
    void
    WindowsGamepadService::confused:hutdown()
    {
    Cleanup();
    }
    void
    WindowsGamepadService::Cleanup()
    {
    if (mXInputPollTimer) {
    mXInputPollTimer->Cancel();
    }
    mGamepads.Clear();
    }
    void
    WindowsGamepadService::DevicesChanged(DeviceChangeType type)
    {
    if (type == DeviceChangeNotification) {
    mObserver->SetDeviceChangeTimer();
    } else if (type == DeviceChangeStable) {
    ScanForDevices();
    }
    }
    NS_IMETHODIMP
    Observer::Observe(nsISupports* aSubject,
    const char* aTopic,
    const char16_t* aData)
    {
    if (strcmp(aTopic, "timer-callback") == 0) {
    mSvc.DevicesChanged(WindowsGamepadService::DeviceChangeStable);
    } else if (strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID) == 0) {
    Stop();
    }
    return NS_OK;
    }
    HWND sHWnd = nullptr;
    bool
    RegisterRawInput(HWND hwnd, bool enable)
    {
    nsTArray<RAWINPUTDEVICE> rid(ArrayLength(kUsagePages));
    rid.SetLength(ArrayLength(kUsagePages));
    for (unsigned i = 0; i < rid.Length(); i++) {
    rid.usUsagePage = kUsagePages.usagePage;
    rid.usUsage = kUsagePages.usage;
    rid.dwFlags =
    enable ? RIDEV_EXINPUTSINK | RIDEV_DEVNOTIFY : RIDEV_REMOVE;
    rid.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(WindowsGamepadService::DeviceChangeNotification);
    }
    }
    break;
    case WM_INPUT:
    if (gService) {
    gService->HandleRawInput(reinterpret_cast<HRAWINPUT>(lParam));
    }
    break;
    }
    return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    } // namespace
    namespace mozilla {
    namespace hal_impl {
    void StartMonitoringGamepadStatus()
    {
    if (gService) {
    return;
    }
    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);
    }
    }
    void StopMonitoringGamepadStatus()
    {
    if (!gService) {
    return;
    }
    if (sHWnd) {
    RegisterRawInput(sHWnd, false);
    DestroyWindow(sHWnd);
    sHWnd = nullptr;
    }
    gService->Shutdown();
    delete gService;
    gService = nullptr;
    }
    } // namespace hal_impl
    } // namespace mozilla
     
    Last edited:

    Users who are viewing this thread

    Top Bottom