Skip to content

Expose Steam Controller touchpads in Gamepad API#15378

Merged
slouken merged 19 commits intolibsdl-org:mainfrom
Kuratius:wip-steamcontroller
Apr 24, 2026
Merged

Expose Steam Controller touchpads in Gamepad API#15378
slouken merged 19 commits intolibsdl-org:mainfrom
Kuratius:wip-steamcontroller

Conversation

@Kuratius
Copy link
Copy Markdown
Contributor

@Kuratius Kuratius commented Apr 13, 2026

Description

Adds the touchpads to the joystick object associated with the steam controller, and updates them when possible.

Existing Issue(s)

Fixes #15359

Should be checked by someone with more experience with the hardware who knows whether the pads are properly centered at (0,0) to decide whether it should be ~ or - for reporting the y-coordinate. The kernel driver does this differently from SDL, so there are two different implementations out there. Overflow is not a problem here.

The cast I added a comment to should probably be changed in the future, but that might be better as its own issue to discuss it. I do not believe it is safe to keep it as is because struct padding is platform dependent.

I copied the syntax for converting from int to sdl's float format from the steam deck code, though I'm not sure this is optimal. Probably depends on whether sdl can be used on soft-float devices. It should be reasonably trivial to keep it mostly as int or fixed point except for the last step when converting to float, even provided that you want the same rounding behavior.

@Kuratius
Copy link
Copy Markdown
Contributor Author

Kuratius commented Apr 13, 2026

Also something I noticed when comparing this to the kernel driver:
The data read via sdl has a noticeable jitter on it, the kernel driver does not. The input from /dev/input feels much cleaner to me. I don't know what causes this.

@Kuratius
Copy link
Copy Markdown
Contributor Author

Kuratius commented Apr 13, 2026

I made two videos with a green pixel showing the touchpad positions to show the jitter.

Kernel-Driver.mp4
SDL-Driver.mp4

The code I'm using to read the kernel driver is fairly simple.
During init

    int fd=-1;
    const char *device = "/dev/input/event26";
    fd = open(device, O_RDONLY | O_NONBLOCK);

    if (fd < 0)
    {
        printf("failed to open device %s\n", device);
        return 1;
    }

and then in the main loop:

#if USE_SDL
        bool isdown=0;
        if (SDL_GetGamepadTouchpadFinger(steamController, 
                                        0, 0, 
                                        &isdown, 
                                        &leftX, &leftY, &pressure))
        {
            if (isdown)
                drawControllerDot(xToWindow((leftX-0.5f)*(1<<16)),yToWindow((leftY-0.5f)*(1<<16)));

        }
        isdown=0;
        if (SDL_GetGamepadTouchpadFinger(steamController, 
                                        1, 0, 
                                        &isdown, 
                                        &rightX, &rightY, &pressure))
        {
            if (isdown)
                drawControllerDot(xToWindow((rightX-0.5f)*(1<<16))+(1280/2),yToWindow((rightY-0.5f)*(1<<16)));
        }
#else
        ssize_t n=-1;
        do
        {
            n = read(fd, &ev, sizeof(ev));
            if (n < (ssize_t)sizeof(ev))
            {
               break;
            }
            if (ev.type == EV_ABS)
            {
                switch (ev.code)
                {
                    case ABS_HAT0X:
                        leftX=ev.value;
                        break;
                    case ABS_HAT0Y:
                        leftY=ev.value;
                        break;
                    case ABS_RX:
                        rightX=ev.value;
                        break;
                    case ABS_RY:
                        rightY=ev.value;
                        break;
                }
            }
        } while(1);
        drawControllerDot(xToWindow(leftX),yToWindow(leftY));
        drawControllerDot(xToWindow(rightX)+(1280/2),yToWindow(rightY));

Note that you need avoid setting SDL_INIT_GAMEPAD, otherwise sdl blocks the /dev/input device.

@Kuratius
Copy link
Copy Markdown
Contributor Author

It's possible the jitter is because SDL doesn't filter the output, while the kernel driver does via a fuzz value, but the difference seems more noticeable than I would expect.
Another thing that I'm not sure about is whether this code should reset to a default value if finger down is not set, or if the user is expected to reject invalid input by checking for whether a finger is down or not before reading from it.
The kernel seems to reset coordinates to the center position if it detects that a finger isn't down.

@Kuratius Kuratius marked this pull request as ready for review April 14, 2026 13:54
Comment thread src/joystick/hidapi/SDL_hidapi_steam.c Outdated
Comment thread src/joystick/hidapi/SDL_hidapi_steam.c Outdated
@slouken
Copy link
Copy Markdown
Collaborator

slouken commented Apr 20, 2026

It's possible the jitter is because SDL doesn't filter the output, while the kernel driver does via a fuzz value, but the difference seems more noticeable than I would expect. Another thing that I'm not sure about is whether this code should reset to a default value if finger down is not set, or if the user is expected to reject invalid input by checking for whether a finger is down or not before reading from it. The kernel seems to reset coordinates to the center position if it detects that a finger isn't down.

It's probably reasonable to do some kind of filtering on the touchpad value. Typically values are passed straight through to the application, but they're reasonably stable on PS4 and DualSense controllers, so we'd want similar behavior here.

The position of a touch release should be the last valid position. SDL_SendJoystickTouchpad() automatically ignores further calls once the touch has been released.

@Kuratius
Copy link
Copy Markdown
Contributor Author

Kuratius commented Apr 23, 2026

I've implemented a moving average filter similar to what the kernel is using, but there is seemingly still some remaining jitter.
It also seems to occasional lose contact of a finger for a single read which can cause the dot representing the position to not be present in that frame.

I believe that it should be possible to at least detect 3 different pressure levels, since the touchpads are clickable.
So maybe it would make sense to report 0 for no finger, 0.5 for a finger but no click and 1.0 for a clicked pad.

Since the click threshold is adjustable it might make sense to have the reported pressure value relate to the threshold in some way, but I'm not sure this is that important.

@Kuratius
Copy link
Copy Markdown
Contributor Author

Kuratius commented Apr 23, 2026

Another issue is that steam still initalizes the right pad as a mouse and the left as a scroll wheel, I don't know how to stop that behavior without having an appid and access to the steamworks documentation.

It works fine when steam isn't running.

Comment thread src/joystick/hidapi/SDL_hidapi_steam.c Outdated
Comment thread src/joystick/hidapi/SDL_hidapi_steam.c Outdated
Comment thread src/joystick/hidapi/SDL_hidapi_steam.c Outdated
Comment thread src/joystick/hidapi/SDL_hidapi_steam.c Outdated
Comment thread src/joystick/hidapi/SDL_hidapi_steam.c Outdated
Comment thread src/joystick/hidapi/SDL_hidapi_steam.c Outdated
Comment thread src/joystick/hidapi/SDL_hidapi_steam.c Outdated
Kuratius and others added 6 commits April 23, 2026 22:03
Also save the touch position in the context so it works with multiple controllers, and added documentation that the touchpads are normally reported as D-Pad and right thumbstick.
@slouken
Copy link
Copy Markdown
Collaborator

slouken commented Apr 24, 2026

Okay, I went ahead and updated the changes for style and made a couple of improvements. If you're happy with the changes, I'll go ahead and merge this.

Comment thread src/joystick/hidapi/SDL_hidapi_steam.c
@Kuratius
Copy link
Copy Markdown
Contributor Author

I added 1 comment, the rest looks fine to me.

@Kuratius
Copy link
Copy Markdown
Contributor Author

You could also think about basing the pressure calculation on the click threshold currently set in the controller, but I'm not sure what value should be reported in that case.

@slouken slouken merged commit 74a7462 into libsdl-org:main Apr 24, 2026
46 checks passed
@slouken
Copy link
Copy Markdown
Collaborator

slouken commented Apr 24, 2026

Merged, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expose Steam Controller touchpads

2 participants