0

I'm trying to read from my XBox 360 controller without polling it. (To be precise, I'm actually using a Logitech F310, but my Windows 10 PC sees it as an XBox 360 controller.) I've written some rather nasty HID code that uses overlapping I/O to block in a thread on two events, one that indicates there is a report ready to read from the HID device, the other indicating the UI thread has requested the HID thread to exit. That works fine, but the HID driver behaves somewhat differently than XInput does. In particular, it consolidates the two triggers into a single value, only passing their difference (on the curious claim that games expect HID values to be 0x80 when the player's finger is off the control). XInput treats them as two distinct values, which is a big improvement. Also, XInput reports the hat switches as four bits, which means you can actually get ten states out of it: unpressed, N, NE, E, SE, S, SW, W, NW, and all-down (that last might be hard to use successfully, but at least it's there if you want it; I've been using it to exit my polling loop).

The downside, to me, of XInput is that there appears to be no way to block on a read request until the controller changes one of its values or buttons. As an HID device, the ReadFile call will block (more exactly, WaitForMultipleEvents blocks until there is data available). XInput seems to anticipate polling. For a game that would naturally be written to poll the controller as often as it updated the game state (maybe once for each new video frame displayed, for example), that makes sense. But if you want to use the controller for some other purpose (I'm working on a theatrical application), you might want a purely asynchronous system like the HID API supplies. But, again, the HID API combines the two value triggers.

Now, when you read the device with XInput, not only do you get the state of all the controls, you also get a packet number. MSDN says the packet number only changes when the state of a control changes. That way, if consecutive packet numbers are the same, you don't have to bother with any processing after the first one, because you know the controller state hasn't changed. But you are still polling which, to me, is somewhat vulgar.

What intrigues me, however, is that when I put a big delay in between my polls (100ms) I can see that the packet numbers go up by more than one when the value controls (the triggers or sticks) are being moved. This, I think, suggests that the device is sending packets without waiting to be polled, and that I am only getting the most recent packet each time I poll. If that is the case, it seems that I ought to be able to block until a packet is sent, and react only when that happens, rather than having to poll at all. But I can't find any indication that this is an option. Because I can block with the HID API, I don't want to give up without trying (including asking for advice here).

Short of writing my own driver for the controller (which I'm not sure is even an option without proprietary documentation), does anyone know how I can use overlapping I/O (or any other blocking method) to read the XBox 360 controller the way XInput does, with the triggers as separate values, and the hat as four buttons?

Below is some code I wrote that reads the controller and shows that the packet numbers can jump by more than one between reads:

#include <Windows.h>
#include <Xinput.h>
#include <stdio.h>

#define MAX_CONTROLLERS 4

int main()
{
    DWORD userIndex;

    XINPUT_STATE xs;
    XINPUT_VIBRATION v;

    XInputEnable(TRUE);

    // Which one are we?

    for (userIndex = 0; userIndex < XUSER_MAX_COUNT; ++userIndex)
        if (XInputGetState(userIndex, &xs) == ERROR_SUCCESS)
            break;

    if (userIndex == XUSER_MAX_COUNT)
    {
        printf("Couldn't find an Xbox 360 controller.\n");
        getchar();
        return -1;
    }

    printf("Using controller #%1d.\n", userIndex);

    while (TRUE)
    {
        DWORD res = XInputGetState(userIndex, &xs);

        printf("%5d %6d: %3d %3d %3d %3d %3d %3d 0x%04X\n",
                res,
            xs.dwPacketNumber,
            xs.Gamepad.bLeftTrigger & 0xFF,
            xs.Gamepad.bRightTrigger & 0xFF,
            xs.Gamepad.sThumbLX & 0xFF,
            xs.Gamepad.sThumbLY & 0xFF,
            xs.Gamepad.sThumbRX & 0xFF,
            xs.Gamepad.sThumbRY & 0xFF,
            xs.Gamepad.wButtons);

        if (xs.Gamepad.wButtons == 0x000F) // mash down the hat
            break;

        Sleep(100);
    }

    getchar();
    return 0;
}

Please note that DirectInput isn't much help, as it also combines the triggers into one value.

Thanks!

Stevens Miller
  • 1,387
  • 8
  • 25

1 Answers1

2

Not sure there is any advantage to this, but could you write a thread that polls on a regular interval and then sets a semaphore (or some other signal) when the state has changed. Then your main thread could block waiting for the signal from the polling thread. But potentially there might not be any advantage to this system because on some controllers the values of the thumbsticks change slightly ever frame whether you move them or not. (Noise) You could of course ignore small changes and only signal your semaphore when a large change occurred.

Chip Burwell
  • 423
  • 3
  • 10
  • Thanks, Chip. That's where I expect I'll end up. Microsoft actually strongly suggests that we code what they call a "dead zone" around the ideal "hands off" value for value controls, precisely because of the noise issue (and my experiments confirm that the sticks don't always return to exactly the same value every time I let go of them although, ironically, the triggers do). A thread that emulates an asynch call to ReadFile would work fine and a call to XInputGetState only takes 20us on my machine. Very light overhead. Just vulgar ;) . – Stevens Miller Aug 24 '16 at 00:49