Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SDL_JoystickSetVirtualAxis handles triggers oddly #6130

Closed
a-hurst opened this issue Aug 25, 2022 · 12 comments
Closed

SDL_JoystickSetVirtualAxis handles triggers oddly #6130

a-hurst opened this issue Aug 25, 2022 · 12 comments

Comments

@a-hurst
Copy link

a-hurst commented Aug 25, 2022

Hi all,

Yesterday I managed to set up an SDL virtual joystick as a bridge between SDL2 and the raw USB packet data from a wired 360 controller (macOS doesn't have a driver for it, and the popular 3rd-party driver no longer works right on Monterey), which means parsing the button/axis events from the controller and sending them to a virtual controller in SDL2.

In the process, I noticed something a little odd: in SDL2, trigger values are always reported as being between 0 (not pressed at all) and 32767 (pressed all the way). However, if I run the following:

SDL_JoystickSetVirtualAxis(stick, SDL_CONTROLLER_AXIS_TRIGGERLEFT, 0)
value = SDL_GameControllerGetAxis(gamepad, SDL_CONTROLLER_AXIS_TRIGGERLEFT)

value becomes 16384 instead of 0. To set the virtual trigger properly, I need to scale the input to SDL_JoystickSetVirtualAxis to the full range of Sint16 (with 0 being -32768 and the midpoint being 0), after which it works properly.

I understand that SDL joysticks themselves don't have a concept of a "trigger axis" versus any other type of axis so I get why this might be tricky to handle on the backend, but this behaviour is still pretty unexpected. If there's no nicer way to handle this in terms of API, this quirk should at least be documented in the Wiki/docstring for the function to reduce future confusion.

Thanks in advance!

EDIT: Maybe an SDL_JoystickSetVirtualTrigger function that respects the normal limits for trigger axes? Would avoid breaking API with anyone currently using SDL_JoystickSetVirtualAxis for triggers, and respect that triggers are handled a bit differently than other axes in SDL2.

@slouken
Copy link
Collaborator

slouken commented Aug 25, 2022

Triggers are actually not handled any differently than other axes in SDL2, but you're expecting them to be, which is the cause of confusion. For what it's worth, using only half the axis range for triggers (and analog buttons) makes a lot of sense, which is why the game controller API exposes it that way. I'll add a note to the documentation, thanks!

@slouken
Copy link
Collaborator

slouken commented Aug 25, 2022

BTW, is there a way to get the HIDAPI driver working for Xbox controllers on macOS? The Xbox controllers aren't HID devices, so they don't show up in the normal device filter...

@a-hurst
Copy link
Author

a-hurst commented Aug 25, 2022

BTW, is there a way to get the HIDAPI driver working for Xbox controllers on macOS? The Xbox controllers aren't HID devices, so they don't show up in the normal device filter...

I've never looked at SDL's HIDAPI internals, but the 360 controller (and its 3rd party variants) all follow a common protocol that's pretty simple to handle with libusb (what my code is using, via pyusb). There's a pretty good explanation/documentation of the USB interface for these controllers here: https://www.partsnotincluded.com/understanding-the-xbox-360-wired-controllers-usb-data/

No clue how easy that would be to graft on to the existing HIDAPI code, but it would be great if it was possible!

@slouken
Copy link
Collaborator

slouken commented Aug 25, 2022

The existing HIDAPI code already supports the 360 controller, I just haven't been able to get at the USB device on macOS. Using hidapi, the controller is never enumerated, and using libusb, the controller is in use by a kernel driver and can't be detached.

@a-hurst
Copy link
Author

a-hurst commented Aug 26, 2022

The existing HIDAPI code already supports the 360 controller, I just haven't been able to get at the USB device on macOS.

The code from the (now discontinued) 360Controller driver for macOS might be helpful here: https://github.com/360Controller/360Controller/blob/d6290c7969dd4b89eeed3f0162a516e4c6e5115c/360Daemon/360Daemon.m#L306

In SDL_hidapi.c, the bit responsible for finding/filtering HID devices on macOS seems to be this bit here:

SDL/src/hidapi/SDL_hidapi.c

Lines 252 to 261 in 3d516b8

SDL_HIDAPI_discovery.m_notificationPort = IONotificationPortCreate(kIOMasterPortDefault);
if (SDL_HIDAPI_discovery.m_notificationPort) {
{
io_iterator_t portIterator = 0;
io_object_t entry;
IOReturn result = IOServiceAddMatchingNotification(
SDL_HIDAPI_discovery.m_notificationPort,
kIOFirstMatchNotification,
IOServiceMatching(kIOHIDDeviceKey),
CallbackIOServiceFunc, NULL, &portIterator);

In the 360Controller code, they seemingly have two codepaths for USB controllers: either they use IOServiceMatching to check explicitly for devices matching their custom Xbox360ControllerClass or they just run IOServiceMatching(kIOUSBDeviceClassName) to get all USB devices and then filter based on product/vendor ID. Since I'm guessing the former approach requires a custom kext to be loaded (?), maybe the latter approach would work?

Alternatively, it looks like you can replace IOServiceMatching by specifying a custom dictionary that lists the properties you'd like the filtered devices to have: https://stackoverflow.com/questions/71887014/macos-iokit-get-services-by-property-name-in-swift

@slouken
Copy link
Collaborator

slouken commented Aug 26, 2022

So I've built libusb, and it can see the controllers, but it looks like at least on macOS 10.12 that another kernel driver has the device locked so userspace applications can't open it.

@a-hurst
Copy link
Author

a-hurst commented Aug 26, 2022

So I've built libusb, and it can see the controllers, but it looks like at least on macOS 10.12 that another kernel driver has the device locked so userspace applications can't open it.

Do you have the 360Controller kext installed by any chance? On Monterey, all I have to do with PyUSB (Python wrapper around libusb-1) to interface with the controller is a) find the device based on pid/vid, b) claim the interface for the device, and c) set the configuration of the device to its first. Maybe it's something that recently changed in macOS?

I've got some older machines with 10.11 and 10.14 handy, I can see if my code works any different on those versions.

@slouken
Copy link
Collaborator

slouken commented Aug 26, 2022

No, I don't have the kext installed. For me, claiming the interface fails.

@a-hurst
Copy link
Author

a-hurst commented Aug 26, 2022

Isn't libusb able to detach kernel drivers for a device in user space? I based my code off an older PyUSB script written for macOS circa ~10.12 and it had a little if-statement that checked for/detached an attached kernel driver for the controller (seemingly without any special permissions). Could very well be necessary on older macOS releases.

@slouken
Copy link
Collaborator

slouken commented Aug 26, 2022

Yes, and the detach fails here.

@a-hurst
Copy link
Author

a-hurst commented Aug 26, 2022

Just tested my PyUSB script on macOS 10.14, it works and reads input events from the controller just the same as on Monterey. However, on my 10.11 machine it errors out when I try to claim the interface, same as you. Looks like something changed in 10.13 or 10.14, then?

EDIT: Disregard that, the libusb binary I was using on my 10.11 machine was problematic. Switching to brew's binary (libusb 1.0.22, a few versions old since brew update is risky on old Macs) fixed the issue, and the test script reads events and changes the LED pattern from the 10.11 machine perfectly fine. Are we both using a wired official 360 controller?

@slouken
Copy link
Collaborator

slouken commented Aug 26, 2022

I updated macOS to 12.5.1 and it's working correctly for me now. :)

PJB3005 pushed a commit to PJB3005/SDL that referenced this issue Oct 5, 2022
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

No branches or pull requests

2 participants