XInput: Comments on core and further steps

Aric Stewart aric at codeweavers.com
Sun Feb 14 20:09:32 CST 2016


Hi Juan,

Awesome thanks for this work. Know I am sure that to get this into wine will require a bit of work so hopefully you are will to go through the process of revisions with us to see things through.


On 2/14/16 7:22 AM, Juan Jose Gonzalez wrote:
> Hi,
> 
> I just published a patch with my xinput core implementation. If anyone
> is interested in an evdev (linux) backend I could publish that too, but
> I guess it won't make it into wine due to the tendency towards the HID
> architecture.

Yeah, we will want to target the HID architecture and I can totally help you on that. I think it should be pretty straight forward. Looking at your back end interface code it all looks really pretty easy. There are parts you have around the mapping that I almost wonder if we want to move into that back end, or maybe a middle layer. I will have to spend more time reading the code before I can come up with anything concrete there. 

> A rough overview on how it's supposed to be used can be read in the
> README file from the second patch. Let me know what you think.
> 
> The next step would be to implement a backend that uses hid.dll. Using
> HID devices should be fairly straightforward. There are, however, two
> parts that I haven't figured out yet:
> 
>   - How can we find new devices? The current work for the HID
> architecture seems to have tackled the hotplugging problem. Can the
> XInput HID backend get notified when a new controller gets plugged in?
> Should it just poll for new devices using SetupDi functions or something
> similar?

There are 2 bits here, hotplugging and device detection. They are 2 separate processes. Speaking on device detection, I think the best thing for us to do would be the process of enumerating the devices via setupapi and selecting the appropriate ones, as you have pointed out with the SetupDi functions. I have code that does this for winejoystick.dev implementation of HID that I have been working on, not sent anywhere yet, and it is not a difficult process.  I will attach my winejoystick.drv module for inspiration/example but it is clearly not ready to actually be submitted as a patch, if for no other reason that all the supporting HID work is not in place yet. 

As for hot plugging that will come through plug and play processes that are still on the drawing board. Probably something eventually involving RegisterDeviceNotification once that gets some love. 

> 
>   - How do we map a controller's buttons and axes to the corresponding
> XInput ones? This has to be done per controller model. My evdev backend
> has a lookup list that matches controllers to a certain mapping using
> some selection criteria (Name, Vendor ID, etc.). My idea was to load the
> mappings from the registry, but for this to be usable by regular users,
> a GUI would have to be implemented to create those registry entries.
> 

I agree that mapping should be allowed for any appropriate controller and not restricting us to particular vendors. (Microsoft)

With HID we get Usages for all our elements. So what we will want to do is probably figure out what usages map to what control elements on an Xbox controller and have that be default mappings. (Here is the HID USB spec for usages http://www.usb.org/developers/hidpage/Hut1_12v2.pdf, windows HID matches these)  so if the A button is page 0x9 Usage 0x1 then by default the element of 0x9 0x1 would be the A button. We would then want to be able to have a way to map other controllers.  I am sure that existing controller maps exist, it would be nice if we could load/use them. 

-aric
-------------- next part --------------
/*
 * Joystick functions using HID
 *
 * Copyright 1997 Andreas Mohr
 * Copyright 1998 Marcus Meissner
 * Copyright 1998,1999 Lionel Ulmer
 * Copyright 2000 Wolfgang Schwotzer
 * Copyright 2000-2001 TransGaming Technologies Inc.
 * Copyright 2002 David Hagood
 * Copyright 2015 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
 *
 */

#include "config.h"
#include "wine/port.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>

#include "joystick.h"

#include "winbase.h"
#include "winreg.h"
#include "wingdi.h"
#include "winnls.h"
#include "winternl.h"
#include "wine/debug.h"

#include "wine/unicode.h"

#include "setupapi.h"
#include "devpkey.h"
#include "hidusage.h"
#include "ddk/hidsdi.h"
#include "initguid.h"
#include "devguid.h"

WINE_DEFAULT_DEBUG_CHANNEL(joystick);

#define MAXJOYSTICK (JOYSTICKID2 + 30)

enum { DRIVER_AXIS_X = 0,
       DRIVER_AXIS_Y,
       DRIVER_AXIS_Z,
       DRIVER_AXIS_RX,
       DRIVER_AXIS_RY,
       DRIVER_AXIS_RZ,
       NUM_AXES};

struct axis {
    int min_value, max_value;
    int value_index;
};

typedef struct tagWINE_JSTCK {
    int   joyIntf;
    BOOL  in_use;

    DWORD nrOfAxes;
    DWORD nrOfButtons;
    DWORD nrOfPOVs;
    struct axis   axes[NUM_AXES];

    HANDLE HIDDevice;
    PHIDP_PREPARSED_DATA HIDData;
    HIDP_CAPS HIDCaps;
    BYTE ReportID;
    PBYTE HIDReport;
    OVERLAPPED overlap;
    HANDLE HIDReportEvent;
} WINE_JSTCK;

static  WINE_JSTCK  JSTCK_Data[MAXJOYSTICK];

static VOID AsyncReadDevice(WINE_JSTCK *jstick)
{
    BOOLEAN status;
    DWORD read;

    memset(&jstick->overlap, 0, sizeof(OVERLAPPED));
    jstick->overlap.hEvent = jstick->HIDReportEvent;

    status = ReadFile(jstick->HIDDevice, jstick->HIDReport,
        jstick->HIDCaps.InputReportByteLength, &read, &jstick->overlap);

    if (status)
        SetEvent(jstick->HIDReportEvent);
}

/**************************************************************************
 *  JSTCK_drvGet    [internal]
 */
static WINE_JSTCK *JSTCK_drvGet(DWORD_PTR dwDevID)
{
    int p;

    if ((dwDevID - (DWORD_PTR)JSTCK_Data) % sizeof(JSTCK_Data[0]) != 0)
        return NULL;
    p = (dwDevID - (DWORD_PTR)JSTCK_Data) / sizeof(JSTCK_Data[0]);
    if (p < 0 || p >= MAXJOYSTICK || !((WINE_JSTCK*)dwDevID)->in_use)
        return NULL;

    return (WINE_JSTCK*)dwDevID;
}

/**************************************************************************
 *  driver_open
 */
LRESULT driver_open(LPSTR str, DWORD dwIntf)
{
    HDEVINFO DeviceInfoSet;
    GUID hidGuid;
    SP_DEVINFO_DATA  DeviceInfoData;
    SP_DEVICE_INTERFACE_DATA DeviceInterfaceInfoData;
    PSP_DEVICE_INTERFACE_DETAIL_DATA_W Data;
    PHIDP_PREPARSED_DATA ppd;
    HANDLE device = INVALID_HANDLE_VALUE;
    HIDP_CAPS Caps;
    DWORD idx,didx;

    if (dwIntf >= MAXJOYSTICK || JSTCK_Data[dwIntf].in_use)
        return 0;

    HidD_GetHidGuid(&hidGuid);
    DeviceInfoSet = SetupDiGetClassDevsW(&hidGuid, NULL, NULL, DIGCF_DEVICEINTERFACE);
    ZeroMemory(&DeviceInfoData, sizeof(DeviceInfoData));
    DeviceInfoData.cbSize = sizeof(DeviceInfoData);
    idx = didx = 0;
    Data = HeapAlloc(GetProcessHeap(), 0 , sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W) + 400);
    Data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W);
    ZeroMemory(&DeviceInterfaceInfoData, sizeof(DeviceInterfaceInfoData));
    DeviceInterfaceInfoData.cbSize = sizeof(DeviceInterfaceInfoData);
    while (SetupDiEnumDeviceInterfaces(DeviceInfoSet, NULL, &hidGuid, idx++,
           &DeviceInterfaceInfoData))
    {
        if (!SetupDiGetDeviceInterfaceDetailW(DeviceInfoSet,
                &DeviceInterfaceInfoData, Data, 400, NULL, NULL))
            continue;

        device = CreateFileW(Data->DevicePath, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0 );
        if (device == INVALID_HANDLE_VALUE)
            continue;

        HidD_GetPreparsedData(device, &ppd);
        HidP_GetCaps(ppd, &Caps);
        if (Caps.UsagePage == HID_USAGE_PAGE_GENERIC &&
            (Caps.Usage == HID_USAGE_GENERIC_GAMEPAD ||
             Caps.Usage == HID_USAGE_GENERIC_JOYSTICK))
        {
            if (didx < dwIntf)
                didx++;
            else
                break;
        }
        CloseHandle(device);
        device = INVALID_HANDLE_VALUE;
        HidD_FreePreparsedData(ppd);
    }
    HeapFree(GetProcessHeap(), 0, Data);
    SetupDiDestroyDeviceInfoList(DeviceInfoSet);

    if (device == INVALID_HANDLE_VALUE)
        return 0;

    JSTCK_Data[dwIntf].joyIntf = dwIntf;
    JSTCK_Data[dwIntf].in_use = TRUE;
    JSTCK_Data[dwIntf].HIDDevice = device;
    JSTCK_Data[dwIntf].HIDData = ppd;
    JSTCK_Data[dwIntf].HIDCaps = Caps;
    JSTCK_Data[dwIntf].HIDReport = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, Caps.InputReportByteLength);
    JSTCK_Data[dwIntf].HIDReportEvent = CreateEventW(NULL, FALSE, FALSE, NULL);

    /* Start a read */
    AsyncReadDevice(&JSTCK_Data[dwIntf]);

    return (LRESULT)&JSTCK_Data[dwIntf];
}

/**************************************************************************
 *  driver_close
 */
LRESULT driver_close(DWORD_PTR dwDevID)
{
    WINE_JSTCK* jstick = JSTCK_drvGet(dwDevID);

    if (jstick == NULL)
        return 0;
    jstick->in_use = FALSE;
    CloseHandle(jstick->HIDDevice);
    HidD_FreePreparsedData(jstick->HIDData);
    HeapFree(GetProcessHeap(), 0, jstick->HIDReport);
    CloseHandle(jstick->HIDReportEvent);
    return 1;
}

/**************************************************************************
 * JoyGetDevCaps    [MMSYSTEM.102]
 */
LRESULT driver_joyGetDevCaps(DWORD_PTR dwDevID, LPJOYCAPSW lpCaps, DWORD dwSize)
{
    WINE_JSTCK* jstick;
    int     i;

    PHIDP_BUTTON_CAPS    pButtonCaps;
    PHIDP_VALUE_CAPS     pValueCaps;
    USHORT               capsLength;

    if ((jstick = JSTCK_drvGet(dwDevID)) == NULL)
        return MMSYSERR_NODRIVER;

    jstick->nrOfButtons = 0;
    pButtonCaps = (PHIDP_BUTTON_CAPS)HeapAlloc(GetProcessHeap(), 0, sizeof(HIDP_BUTTON_CAPS) * jstick->HIDCaps.NumberInputButtonCaps);
    capsLength = jstick->HIDCaps.NumberInputButtonCaps;
    HidP_GetButtonCaps(HidP_Input, pButtonCaps, &capsLength, jstick->HIDData);
    for (i = 0; i < capsLength; i++)
    {
        if (pButtonCaps[i].UsagePage != HID_USAGE_PAGE_BUTTON)
            continue;
        if (pButtonCaps[i].IsRange)
            jstick->nrOfButtons += (pButtonCaps[i].Range.UsageMax - pButtonCaps[i].Range.UsageMin + 1);
        else
            jstick->nrOfButtons++;
        jstick->ReportID = pButtonCaps[i].ReportID;
    }
    HeapFree(GetProcessHeap(), 0, pButtonCaps);

    pValueCaps = (PHIDP_VALUE_CAPS)HeapAlloc(GetProcessHeap(), 0, sizeof(HIDP_VALUE_CAPS) * jstick->HIDCaps.NumberInputValueCaps);
    capsLength = jstick->HIDCaps.NumberInputValueCaps;
    HidP_GetValueCaps(HidP_Input, pValueCaps, &capsLength, jstick->HIDData);
    jstick->nrOfAxes = 0;
    jstick->nrOfPOVs = 0;

    for (i = 0; i < capsLength; i++)
    {
        if (pValueCaps[i].UsagePage != HID_USAGE_PAGE_GENERIC)
            continue;
        if (pValueCaps[i].NotRange.Usage == HID_USAGE_GENERIC_HATSWITCH)
            jstick->nrOfPOVs++;
        else
            jstick->nrOfAxes++;
    }

    HidD_GetProductString(jstick->HIDDevice, lpCaps->szPname, sizeof(lpCaps->szPname));

    TRACE("Name: %s, #Axes: %d, #Buttons: %d #POVs: %d\n",
      debugstr_w(lpCaps->szPname), jstick->nrOfAxes, jstick->nrOfButtons, jstick->nrOfPOVs);

    lpCaps->wMid = MM_MICROSOFT;
    lpCaps->wPid = MM_PC_JOYSTICK;
    lpCaps->szPname[MAXPNAMELEN-1] = '\0';
    lpCaps->wXmin = 0;
    lpCaps->wXmax = 0xFFFF;
    lpCaps->wYmin = 0;
    lpCaps->wYmax = 0xFFFF;
    lpCaps->wZmin = 0;
    lpCaps->wZmax = (jstick->nrOfAxes >= 3) ? 0xFFFF : 0;
    lpCaps->wNumButtons = jstick->nrOfButtons;
    if (dwSize == sizeof(JOYCAPSW)) {
        /* complete 95 structure */
        lpCaps->wRmin = 0;
        lpCaps->wRmax = 0xFFFF;
        lpCaps->wUmin = 0;
        lpCaps->wUmax = 0xFFFF;
        lpCaps->wVmin = 0;
        lpCaps->wVmax = 0xFFFF;
        lpCaps->wMaxAxes = 6; /* same as MS Joystick Driver */
        lpCaps->wNumAxes = 0; /* nr of axes in use */
        lpCaps->wMaxButtons = 32; /* same as MS Joystick Driver */
        lpCaps->szRegKey[0] = 0;
        lpCaps->szOEMVxD[0] = 0;
        lpCaps->wCaps = 0;

        /* blank out the axes */
        for (i = 0; i < NUM_AXES; i++)
            jstick->axes[i].value_index = -1;

        for (i = 0; i < capsLength; i++) {
            if (pValueCaps[i].UsagePage != HID_USAGE_PAGE_GENERIC)
                continue;
            if (pValueCaps[i].NotRange.Usage >= HID_USAGE_GENERIC_X &&
                pValueCaps[i].NotRange.Usage <= HID_USAGE_GENERIC_RZ)
            {
                int idx;
                idx = pValueCaps[i].NotRange.Usage - HID_USAGE_GENERIC_X;
                jstick->axes[idx].value_index = i;
                jstick->axes[idx].min_value = pValueCaps[i].LogicalMin;
                jstick->axes[idx].max_value = pValueCaps[i].LogicalMax;
            }
            else
                continue;
            switch (pValueCaps[i].NotRange.Usage) {
                case HID_USAGE_GENERIC_X:
                case HID_USAGE_GENERIC_Y:
                    lpCaps->wNumAxes++;
                    break;
                case HID_USAGE_GENERIC_Z:
                    lpCaps->wNumAxes++;
                    lpCaps->wCaps |= JOYCAPS_HASZ;
                    break;
                case HID_USAGE_GENERIC_RZ:
                    lpCaps->wNumAxes++;
                    lpCaps->wCaps |= JOYCAPS_HASR;
                    break;
                case HID_USAGE_GENERIC_RX:
                    lpCaps->wNumAxes++;
                    lpCaps->wCaps |= JOYCAPS_HASU;
                    break;
                case HID_USAGE_GENERIC_RY:
                    lpCaps->wNumAxes++;
                    lpCaps->wCaps |= JOYCAPS_HASV;
                    break;
                default:
                    WARN("Unknown axis %i(%u). Skipped.\n",
                        pValueCaps[i].NotRange.Usage, i);
            }
        }
        if (jstick->nrOfPOVs > 0)
            lpCaps->wCaps |= JOYCAPS_HASPOV | JOYCAPS_POV4DIR;
    }
    HeapFree(GetProcessHeap(), 0, pValueCaps);

    return JOYERR_NOERROR;
}

/**************************************************************************
 *  driver_joyGetPos
 */
LRESULT driver_joyGetPosEx(DWORD_PTR dwDevID, LPJOYINFOEX lpInfo)
{
    static const struct {
        DWORD flag;
        off_t offset;
        USHORT usage;
    } axis_map[NUM_AXES] = {
        { JOY_RETURNX, FIELD_OFFSET(JOYINFOEX, dwXpos), HID_USAGE_GENERIC_X},
        { JOY_RETURNY, FIELD_OFFSET(JOYINFOEX, dwYpos), HID_USAGE_GENERIC_Y},
        { JOY_RETURNZ, FIELD_OFFSET(JOYINFOEX, dwZpos), HID_USAGE_GENERIC_Z},
        { JOY_RETURNU, FIELD_OFFSET(JOYINFOEX, dwUpos), HID_USAGE_GENERIC_RX},
        { JOY_RETURNV, FIELD_OFFSET(JOYINFOEX, dwVpos), HID_USAGE_GENERIC_RY},
        { JOY_RETURNR, FIELD_OFFSET(JOYINFOEX, dwRpos), HID_USAGE_GENERIC_RZ},
    };

    WINE_JSTCK* jstick;
    int i;
    DWORD rc;

    if ((jstick = JSTCK_drvGet(dwDevID)) == NULL)
        return MMSYSERR_NODRIVER;

    /* Queue another read */
    rc = WaitForSingleObject(jstick->HIDReportEvent, 1);
    if (rc == WAIT_OBJECT_0)
        AsyncReadDevice(jstick);

    if (lpInfo->dwFlags & JOY_RETURNBUTTONS)
    {
        USAGE usage[32];
        ULONG usageLength;

        lpInfo->dwButtonNumber = 0;
        lpInfo->dwButtons = 0x0;
        usageLength = min(jstick->nrOfButtons, 32);

        HidP_GetUsages(HidP_Input, HID_USAGE_PAGE_BUTTON, 0, usage, &usageLength,
            jstick->HIDData, (PCHAR)jstick->HIDReport,
            jstick->HIDCaps.InputReportByteLength);

        for (i = 0; i < usageLength; i++)
        {
            lpInfo->dwButtons |= (1 << usage[i]);
            lpInfo->dwButtonNumber++;
        }
    }

    for (i = 0; i < NUM_AXES; i++)
    {
        if (lpInfo->dwFlags & axis_map[i].flag)
        {
            DWORD* field = (DWORD*)((char*)lpInfo + axis_map[i].offset);
            ULONG dev_value;
            int value;

            HidP_GetUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0,
                axis_map[i].usage, &dev_value, jstick->HIDData,
                (PCHAR)jstick->HIDReport, jstick->HIDCaps.InputReportByteLength);

            value = dev_value - jstick->axes[i].min_value;
            *field = MulDiv(value, 0xFFFF, jstick->axes[i].max_value - jstick->axes[i].min_value);
        }
    }

    if (lpInfo->dwFlags & JOY_RETURNPOV && jstick->nrOfPOVs > 0) {
        ULONG dev_value;
        HidP_GetUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, 0,
            HID_USAGE_GENERIC_HATSWITCH, &dev_value, jstick->HIDData,
            (PCHAR)jstick->HIDReport, jstick->HIDCaps.InputReportByteLength);

        if (dev_value >= 8)
            lpInfo->dwPOV = JOY_POVCENTERED;
        else
            lpInfo->dwPOV = dev_value * 4500;
    }

    TRACE("x: %d, y: %d, z: %d, r: %d, u: %d, v: %d, buttons: 0x%04x, flags: 0x%04x\n",
      lpInfo->dwXpos, lpInfo->dwYpos, lpInfo->dwZpos,
      lpInfo->dwRpos, lpInfo->dwUpos, lpInfo->dwVpos,
      lpInfo->dwButtons, lpInfo->dwFlags);

    return JOYERR_NOERROR;
}

/**************************************************************************
 * driver_joyGetPos
 */
LRESULT driver_joyGetPos(DWORD_PTR dwDevID, LPJOYINFO lpInfo)
{
    JOYINFOEX   ji;
    LONG        ret;

    memset(&ji, 0, sizeof(ji));

    ji.dwSize = sizeof(ji);
    ji.dwFlags = JOY_RETURNX | JOY_RETURNY | JOY_RETURNZ | JOY_RETURNBUTTONS;
    ret = driver_joyGetPosEx(dwDevID, &ji);
    if (ret == JOYERR_NOERROR)  {
        lpInfo->wXpos    = ji.dwXpos;
        lpInfo->wYpos    = ji.dwYpos;
        lpInfo->wZpos    = ji.dwZpos;
        lpInfo->wButtons = ji.dwButtons;
    }

    return ret;
}


More information about the wine-devel mailing list