diff --git a/src/arch/Lights/LightsDriver_Linux_stac.cpp b/src/arch/Lights/LightsDriver_Linux_stac.cpp index 3baccf78a9..3737ec906d 100644 --- a/src/arch/Lights/LightsDriver_Linux_stac.cpp +++ b/src/arch/Lights/LightsDriver_Linux_stac.cpp @@ -25,264 +25,87 @@ REGISTER_LIGHTS_DRIVER_CLASS2(stac, Linux_stac); -StacDevice::StacDevice(uint8_t pn) -{ - memset(outputBuffer, 0x00, sizeof(outputBuffer)); - - deviceVID = STAC_VID; - playerNumber = pn; - - switch (playerNumber) - { - case 1: - devicePID = STAC_PID_P1; - break; - - case 2: - devicePID = STAC_PID_P2; - break; - - default: - LOG->Warn("STAC: Invalid player number instantiated, no PID set."); - break; - } -} - -//function adapted from example in: -//https://web.archive.org/web/20190312223426/http://www.signal11.us/oss/udev/ -//allows us to find the /dev/hidraw device that matches the Snek usb VID and PID. -void StacDevice::FindDevice() -{ - struct udev *udev; - struct udev_enumerate *enumerate; - struct udev_list_entry *devices, *dev_list_entry; - struct udev_device *dev; - - const char *devVID; - const char *devPID; - - bool found = false; - - /* Create the udev object */ - udev = udev_new(); - if (!udev) - { - LOG->Warn("Can't create udev"); - return; - } - - /* Create a list of the devices in the 'hidraw' subsystem. */ - enumerate = udev_enumerate_new(udev); - udev_enumerate_add_match_subsystem(enumerate, "hidraw"); - udev_enumerate_scan_devices(enumerate); - devices = udev_enumerate_get_list_entry(enumerate); - /* For each item enumerated, print out its information. - udev_list_entry_foreach is a macro which expands to - a loop. The loop will be executed for each member in - devices, setting dev_list_entry to a list entry - which contains the device's path in /sys. */ - udev_list_entry_foreach(dev_list_entry, devices) - { - const char *path; - - /* Get the filename of the /sys entry for the device - and create a udev_device object (dev) representing it */ - path = udev_list_entry_get_name(dev_list_entry); - dev = udev_device_new_from_syspath(udev, path); - - devicePath = udev_device_get_devnode(dev); - - /* The device pointed to by dev contains information about - the hidraw device. In order to get information about the - USB device, get the parent device with the - subsystem/devtype pair of "usb"/"usb_device". This will - be several levels up the tree, but the function will find - it.*/ - dev = udev_device_get_parent_with_subsystem_devtype( - dev, - "usb", - "usb_device"); - - if (!dev) - { - LOG->Warn("Unable to find parent usb device."); - return; - } - - devVID = udev_device_get_sysattr_value(dev, "idVendor"); - devPID = udev_device_get_sysattr_value(dev, "idProduct"); - - udev_device_unref(dev); - - if (strcmp(devVID, deviceVID) == 0 && - strcmp(devPID, devicePID) == 0) - { - found = true; - break; - } - } - - /* Free the enumerator object */ - udev_enumerate_unref(enumerate); - - udev_unref(udev); - - if (!found) - { - devicePath = nullptr; - } -} - -void StacDevice::Connect() -{ - devicePath = nullptr; - fd = -1; - - FindDevice(); - - if (devicePath == nullptr) - { - LOG->Warn("stac pn%d not found. Please ensure device is connected to system.", playerNumber); - } - else - { - fd = open(devicePath, O_RDWR | O_NONBLOCK); - - if (!IsConnected()) - { - LOG->Warn("Could not open stac pn%d at %s. Check device permissions or udev rules. errno: %d: %s", playerNumber, devicePath, errno, strerror(errno)); - } - else - { - LOG->Info("Successfully opened stac pn%d at: %s", playerNumber, devicePath); - - //ensure the device, when connected, gets a fresh state. - newState = true; - } - } -} - -void StacDevice::Close() -{ - if (fd >= 0) - { - close(fd); - - LOG->Info("Closed stac pn%d at: %s", playerNumber, devicePath); - - fd = -1; - } -} - -void StacDevice::SetInBuffer(int index, bool lightState) -{ - //the first byte is the report ID, so we offset it here to adjust. - uint8_t index_offset = index + 1; - - //each index in the array represents a single light, - //the light will turn on for any value that isn't 0x00 - uint8_t val = lightState ? 0xFF : 0x00; - - //ensure the index is valid and the light value has changed. - if (index_offset < STAC_HIDREPORT_SIZE && outputBuffer[index_offset] != val) - { - outputBuffer[index_offset] = val; - - //signal the loop to push the new buffer to the device. - newState = true; - } -} - -void StacDevice::PushBufferToDevice() -{ - //first check to make sure we have *new* data to send to the device. - if (newState) - { - //Always ensure there is a report ID before writing, - //otherwise linux will complain and throw errors. - outputBuffer[0] = STAC_REPORT_ID; - - int res = write(fd, outputBuffer, STAC_HIDREPORT_SIZE); - - if (res < 0) - { - LOG->Warn("stac pn%d Write Error: %d: %s", playerNumber, errno, strerror(errno)); - - //close on error write to signal a reconnect. - Close(); - } - - newState = false; - } -} - LightsDriver_Linux_stac::LightsDriver_Linux_stac() { - //attempt to connect on game launch - stacPlayer1->Connect(); - stacPlayer2->Connect(); + memset(outputBuffer[GameController_1], 0x00, STAC_HIDREPORT_SIZE); + memset(outputBuffer[GameController_2], 0x00, STAC_HIDREPORT_SIZE); - //we keep track of if the device has *ever* been connected to signal - //if we want to try to reconnect. - //because if there is no P2 stac connected, then we don't want to go - //searching for something that was *never* there. - haveSeenP1 = stacPlayer1->IsConnected(); - haveSeenP2 = stacPlayer2->IsConnected(); + // Open the device using the VID, PID, + // and optionally the Serial number. + handleP1 = hid_open(STAC_VID, STAC_PID_P1, NULL); + handleP2 = hid_open(STAC_VID, STAC_PID_P2, NULL); + + if (!handleP1) { + LOG->Warn("Stac P1 device not found."); + hid_exit(); + return; + } } LightsDriver_Linux_stac::~LightsDriver_Linux_stac() { - stacPlayer1->Close(); - stacPlayer2->Close(); + if(handleP1) + hid_close(handleP1); + if (handleP2) + hid_close(handleP2); - delete stacPlayer1; - delete stacPlayer2; + // Finalize the hidapi library + hid_exit(); } -void LightsDriver_Linux_stac::HandleState(const LightsState *ls, StacDevice *dev, GameController ctrlNum) +void LightsDriver_Linux_stac::SetBuffer(int index, bool lightState, GameController ctrlNum) { + //the first byte is the report ID, so we offset it here to adjust. + uint8_t index_offset = index + 1; + + //each index in the array represents a single light, + //the light will turn on for any value that isn't 0x00 + uint8_t val = lightState ? 0xFF : 0x00; + + //ensure the index is valid and the light value has changed. + if (index_offset < STAC_HIDREPORT_SIZE && outputBuffer[ctrlNum][index_offset] != val) + { + outputBuffer[ctrlNum][index_offset] = val; + + //signal the loop to push the new buffer to the device. + stateChanged = true; + } +} + +void LightsDriver_Linux_stac::HandleState(const LightsState *ls, GameController ctrlNum) +{ + if (!handle[ctrlNum]) + return; + //check to see which game we are running as it can change during gameplay. const InputScheme *pInput = &GAMESTATE->GetCurrentGame()->m_InputScheme; RString sInputName = pInput->m_szName; if (sInputName.EqualsNoCase("dance")) { - dev->SetInBuffer(STAC_LIGHTINDEX_BTN1, ls->m_bGameButtonLights[ctrlNum][DANCE_BUTTON_UP]); - dev->SetInBuffer(STAC_LIGHTINDEX_BTN2, ls->m_bGameButtonLights[ctrlNum][DANCE_BUTTON_DOWN]); - dev->SetInBuffer(STAC_LIGHTINDEX_BTN3, ls->m_bGameButtonLights[ctrlNum][DANCE_BUTTON_LEFT]); - dev->SetInBuffer(STAC_LIGHTINDEX_BTN4, ls->m_bGameButtonLights[ctrlNum][DANCE_BUTTON_RIGHT]); - } - else if (sInputName.EqualsNoCase("pump")) - { - dev->SetInBuffer(STAC_LIGHTINDEX_BTN1, ls->m_bGameButtonLights[ctrlNum][PUMP_BUTTON_UPLEFT]); - dev->SetInBuffer(STAC_LIGHTINDEX_BTN2, ls->m_bGameButtonLights[ctrlNum][PUMP_BUTTON_UPRIGHT]); - dev->SetInBuffer(STAC_LIGHTINDEX_BTN3, ls->m_bGameButtonLights[ctrlNum][PUMP_BUTTON_CENTER]); - dev->SetInBuffer(STAC_LIGHTINDEX_BTN4, ls->m_bGameButtonLights[ctrlNum][PUMP_BUTTON_DOWNLEFT]); - dev->SetInBuffer(STAC_LIGHTINDEX_BTN5, ls->m_bGameButtonLights[ctrlNum][PUMP_BUTTON_DOWNRIGHT]); + SetBuffer(STAC_LIGHTINDEX_BTN1, ls->m_bGameButtonLights[ctrlNum][DANCE_BUTTON_UP], ctrlNum); + SetBuffer(STAC_LIGHTINDEX_BTN2, ls->m_bGameButtonLights[ctrlNum][DANCE_BUTTON_DOWN], ctrlNum); + SetBuffer(STAC_LIGHTINDEX_BTN3, ls->m_bGameButtonLights[ctrlNum][DANCE_BUTTON_LEFT], ctrlNum); + SetBuffer(STAC_LIGHTINDEX_BTN4, ls->m_bGameButtonLights[ctrlNum][DANCE_BUTTON_RIGHT], ctrlNum); + } + else if (sInputName.EqualsNoCase("pump")) + { + SetBuffer(STAC_LIGHTINDEX_BTN1, ls->m_bGameButtonLights[ctrlNum][PUMP_BUTTON_UPLEFT], ctrlNum); + SetBuffer(STAC_LIGHTINDEX_BTN2, ls->m_bGameButtonLights[ctrlNum][PUMP_BUTTON_UPRIGHT], ctrlNum); + SetBuffer(STAC_LIGHTINDEX_BTN3, ls->m_bGameButtonLights[ctrlNum][PUMP_BUTTON_CENTER], ctrlNum); + SetBuffer(STAC_LIGHTINDEX_BTN4, ls->m_bGameButtonLights[ctrlNum][PUMP_BUTTON_DOWNLEFT], ctrlNum); + SetBuffer(STAC_LIGHTINDEX_BTN5, ls->m_bGameButtonLights[ctrlNum][PUMP_BUTTON_DOWNRIGHT], ctrlNum); } - dev->PushBufferToDevice(); + if (!stateChanged) + return; + + hid_write(handle[ctrlNum], (unsigned char*)&outputBuffer[ctrlNum], STAC_HIDREPORT_SIZE); + stateChanged = false; } void LightsDriver_Linux_stac::Set(const LightsState *ls) { - if (stacPlayer1->IsConnected()) - { - HandleState(ls, stacPlayer1, GameController_1); - } - else if (haveSeenP1) - { - //only attempt to connect again if it was connected on launch. - stacPlayer1->Connect(); - } - - if (stacPlayer2->IsConnected()) - { - HandleState(ls, stacPlayer2, GameController_2); - } - else if (haveSeenP2) - { - //only attempt to connect again if it was connected on launch. - stacPlayer2->Connect(); - } + HandleState(ls, GameController_1); + HandleState(ls, GameController_2); } diff --git a/src/arch/Lights/LightsDriver_Linux_stac.h b/src/arch/Lights/LightsDriver_Linux_stac.h index 755860836d..545628198d 100644 --- a/src/arch/Lights/LightsDriver_Linux_stac.h +++ b/src/arch/Lights/LightsDriver_Linux_stac.h @@ -21,6 +21,7 @@ #include "arch/Lights/LightsDriver.h" #include +#include "hidapi.h" //static information about the device(s) in question. #define STAC_VID "04d8" @@ -43,50 +44,16 @@ enum StacLightIndex STAC_LIGHTINDEX_MAX }; -class StacDevice -{ -public: - const char *deviceVID; - const char *devicePID; - - const char *devicePath; - int fd = -1; - - uint8_t playerNumber = 0; - bool newState = false; - - uint8_t outputBuffer[STAC_HIDREPORT_SIZE]; - - StacDevice(uint8_t pn); - - void FindDevice(); - void Connect(); - void Close(); - - bool IsConnected() - { - //a zero/positive file descriptor is a valid one. - //and if we have opened it, then we are connected. - //if there is an error, then we close it. - return (fd >= 0); - } - - void SetInBuffer(int index, bool lightState); - - void PushBufferToDevice(); -}; - class LightsDriver_Linux_stac : public LightsDriver { private: - StacDevice *stacPlayer1 = new StacDevice(1); - StacDevice *stacPlayer2 = new StacDevice(2); + hid_device* handle[2]; - bool haveSeenP1 = false; - bool haveSeenP2 = false; + bool stateChanged = false; + uint8_t outputBuffer[2][STAC_HIDREPORT_SIZE]; void HandleState(const LightsState *ls, StacDevice *dev, GameController ctrlNum); - + void SetBuffer(int index, bool lightState, GameController ctrlNum); public: LightsDriver_Linux_stac(); virtual ~LightsDriver_Linux_stac();