From c83716e480d27e7ba0058e09291838bc529401a7 Mon Sep 17 00:00:00 2001 From: Tim Patton <38817597+pattontim@users.noreply.github.com> Date: Tue, 21 Mar 2023 16:44:54 -0400 Subject: [PATCH 1/3] Write BT driver targeting windows --- include/scc/driver.h | 2 +- include/scc/input_device.h | 2 +- make-win32-release.sh | 2 +- src/config/defaults.c | 2 +- src/daemon/drivers/meson.build | 10 ++ src/daemon/drivers/sc/by_bt.c | 267 +++++++++++++++++++++++++++++++++ src/daemon/drivers/sc/common.c | 155 ++++++++++++++----- src/daemon/drivers/sc/sc.h | 75 ++++++++- src/daemon/input_bsd.c | 2 +- src/daemon/input_hidapi.c | 38 +++-- src/daemon/input_libusb.c | 2 +- 11 files changed, 505 insertions(+), 52 deletions(-) create mode 100644 src/daemon/drivers/sc/by_bt.c diff --git a/include/scc/driver.h b/include/scc/driver.h index f83e0b4c1..91b03772e 100644 --- a/include/scc/driver.h +++ b/include/scc/driver.h @@ -18,8 +18,8 @@ typedef unsigned short Product; typedef uintptr_t TaskID; typedef enum { USB = 0, -#ifdef __linux__ BT = 1, +#ifdef __linux__ EVDEV = 2, #endif #ifdef __BSD__ diff --git a/include/scc/input_device.h b/include/scc/input_device.h index d084c4472..c24b4bae7 100644 --- a/include/scc/input_device.h +++ b/include/scc/input_device.h @@ -125,7 +125,7 @@ typedef struct InputDevice { * * 'idx' has meaning only with libusb devices and is ignored for everything else. */ - uint8_t* (*hid_request)(InputDevice* dev, uint16_t idx, uint8_t* data, int32_t length); + uint8_t* (*hid_request)(InputDevice* dev, int16_t idx, uint8_t* data, int32_t length); } InputDevice; diff --git a/make-win32-release.sh b/make-win32-release.sh index 0ce4111c3..00091ffad 100644 --- a/make-win32-release.sh +++ b/make-win32-release.sh @@ -37,7 +37,7 @@ GTK_ICONS=( ui/pan-end-symbolic.symbolic.png ui/pan-up-symbolic.symbolic.png ) -DRIVERS=(sc_by_cable sc_dongle) +DRIVERS=(sc_by_cable sc_dongle sc_by_bt) export PROCESSOR_ARCHITEW6432=x86 # meson $1 diff --git a/src/config/defaults.c b/src/config/defaults.c index 3c48d3088..ad90d1b07 100644 --- a/src/config/defaults.c +++ b/src/config/defaults.c @@ -24,7 +24,7 @@ const char* DEFAULT_PROFILES[] = { * Default list of enabled drivers. If driver is not listed in "drivers" * object, it's enabled only if listed here. */ -const char* DEFAULT_ENABLED_DRIVERS[] = { "sc_by_cable" }; +const char* DEFAULT_ENABLED_DRIVERS[] = { "sc_by_cable sc_by_bt" }; const struct config_item DEFAULTS[] = { diff --git a/src/daemon/drivers/meson.build b/src/daemon/drivers/meson.build index 2c62c0039..f0dd0958d 100644 --- a/src/daemon/drivers/meson.build +++ b/src/daemon/drivers/meson.build @@ -88,5 +88,15 @@ if host_machine.system() == 'windows' 'generic/dinput.c', ] ) + + shared_library('scc-drv-sc_by_bt', + include_directories: [ include ], + link_with: [ utils_lib, config_lib ], + dependencies: deps, + sources: [ + 'sc/common.c', + 'sc/by_bt.c', + ] + ) endif diff --git a/src/daemon/drivers/sc/by_bt.c b/src/daemon/drivers/sc/by_bt.c new file mode 100644 index 000000000..351dd7bc8 --- /dev/null +++ b/src/daemon/drivers/sc/by_bt.c @@ -0,0 +1,267 @@ +/** + * Steam Controller Controller Steam Controller Driver + * + * Used to communicate with single Steam Controller + * connected directly by USB cable. + */ +#define LOG_TAG "sc_by_bt" +#include "scc/utils/logging.h" +#include "scc/utils/assert.h" +#include "scc/input_device.h" +#include "scc/input_test.h" +#include "scc/driver.h" +#include "scc/mapper.h" +#include "scc/tools.h" +#include "sc.h" +#include + +#define ENDPOINT 3 +#define CONTROLIDX -1 +#define CHUNK_LENGTH 64 +#define VENDOR_ID 0x28de +#define PRODUCT_ID 0x1106 +#define PACKET_SIZE 20 +#define LONG_PACKET 0x80 +#define BT_BUTTONS_BITS 23 + +static controller_available_cb controller_available = NULL; + +enum BtInPacketType { + BUTTON = 0x0010, + TRIGGERS = 0x0020, + STICK = 0x0080, + LPAD = 0x0100, + RPAD = 0x0200, + GYRO = 0x1800, + PING = 0x5000, +}; + +static inline void debug_packet(char* buffer, size_t size) { + size_t i; + for(i=0; imapper != NULL){ + if (sc->long_packet) { + // Previous packet had long flag set and this is its 2nd part + memcpy(ptr_buffer + PACKET_SIZE, tmp_buffer + 1, PACKET_SIZE - 1); + sc->long_packet = 0; + } else { + // This is 1st part of long packet + memcpy(ptr_buffer, (void *)i, PACKET_SIZE); + sc->long_packet = *((uint8_t*)(ptr_buffer + 1)) == LONG_PACKET; + if (sc->long_packet) { + return 0; + } + // debug_packet(ptr->buffer, PACKET_SIZE); + } + + int rv = 0; + int bit; + uint16_t type = *((uint16_t*)(ptr_buffer + 2)); + char* data = &ptr_buffer[4]; + if ((type & PING) == PING) { + // PING packet does nothing + return 0; + } + if ((type & BUTTON) == BUTTON) { + rv = 1; + uint32_t bt_buttons = *((uint32_t*)data); + uint32_t sc_buttons = 0; + //TODO cover remaining bits + for (bit=0; bit>= 1; + } + sc->input.buttons = (SCButton)sc_buttons; + data += 3; + } + if ((type & TRIGGERS) == TRIGGERS) { + rv = 1; + sc->input.ltrig = *(((uint8_t*)data) + 0); + sc->input.rtrig = *(((uint8_t*)data) + 1); + data += 2; + } + if ((type & STICK) == STICK) { + rv = 1; + sc->input.stick_x = *(((int16_t*)data) + 0); + sc->input.stick_y = *(((int16_t*)data) + 1); + data += 4; + } + if ((type & LPAD) == LPAD) { + rv = 1; + sc->input.lpad_x = *(((int16_t*)data) + 0); + sc->input.lpad_y = *(((int16_t*)data) + 1); + data += 4; + } + if ((type & RPAD) == RPAD) { + rv = 1; + sc->input.rpad_x = *(((int16_t*)data) + 0); + sc->input.rpad_y = *(((int16_t*)data) + 1); + data += 4; + } + if ((type & GYRO) == GYRO) { + rv = 1; + sc->input.gyro.gpitch = *(((int16_t*)data) + 0); + sc->input.gyro.groll = *(((int16_t*)data) + 1); + sc->input.gyro.gyaw = *(((int16_t*)data) + 2); + sc->input.gyro.q0 = *(((int16_t*)data) + 3); + sc->input.gyro.q1 = *(((int16_t*)data) + 4); + sc->input.gyro.q2 = *(((int16_t*)data) + 5); + sc->input.gyro.q3 = *(((int16_t*)data) + 6); + data += 14; + } + sc->input.buttons &= ~0b00000000000011110000000000000000; + //input eval same bitmap? + //sc->mapper->input(sc->mapper, &sc->input); + return rv; + } +} + +void input_interrupt_cb(Daemon* d, InputDevice* dev, uint8_t endpoint, const uint8_t* data, void* userdata) { + SCController* sc = (SCController*)userdata; + if (data == NULL) { + // Means controller disconnected (or failed in any other way) + DEBUG("%s disconnected", sc->desc); + // USBHelper* usb = d->get_usb_helper(); + // usb->close(sc->usb_hndl); + // TODO: Calling close at this point may hang. Closing should be + // scheduled for later time instead, ideally in sccd_usb_dev_close. + disconnected(sc); + // TODO: Deallocate sc + return; + } + //debug_packet((char *)data, PACKET_SIZE * 2); + int status = bt_handle_input(sc, data); + if(status == 1){ + //TODO input rotation support + /*if ( + //self._input_rotation_l and + (self._state.type & 0x0100) != 0) { + lx, ly = self._state.lpad_x, self._state.lpad_y + //s, c = sin(self._input_rotation_l), cos(self._input_rotation_l) + self._state.lpad_x = int(lx * c - ly * s) + self._state.lpad_y = int(lx * s + ly * c) + } + if ( + //self._input_rotation_r and + (self._state.type & 0x0200) != 0) { + rx, ry = self._state.rpad_x, self._state.rpad_y + s, c = sin(self._input_rotation_r), cos(self._input_rotation_r) + self._state.rpad_x = int(rx * c - ry * s) + self._state.rpad_y = int(rx * s + ry * c) + }*/ + + sc->mapper->input(sc->mapper, &sc->input); + + //it's unlikely this is necessary + //flush() + } else if(status > 1) { + DDEBUG("Read Failed"); + + //TODO maybe should retry + //self.close() + //self.driver.retry(self.syspath) + } +} + + +static bool hotplug_cb(Daemon* daemon, const InputDeviceData* idata) { + if (controller_available != NULL) { + controller_available("sc_by_bt", 9, idata); + return true; + } + SCController* sc = NULL; + DDEBUG("%s",idata->path); + InputDevice* dev = idata->open(idata); + if (dev == NULL) { + LERROR("Failed to open '%s'", idata->path); + return true; // and nothing happens + } + if ((sc = create_usb_controller(daemon, dev, SC_BT, CONTROLIDX)) == NULL) { + LERROR("Failed to allocate memory"); + goto hotplug_cb_fail; + } + if (dev->sys == USB) { + if (dev->claim_interfaces_by(dev, 3, 0, 0) <= 0) { + LERROR("Failed to claim interfaces"); + goto hotplug_cb_fail; + } + } + //TODO fix serial grabbing + if (!read_serial(sc)) { + LERROR("Failed to read serial number"); + goto hotplug_cb_failed_to_configure; + } + //TODO needed? + if (!clear_mappings(sc)) + // clear_mappings is needed on Windows, as kernel driver cannot be deatached there + goto hotplug_cb_failed_to_configure; + if (!configure(sc)) + goto hotplug_cb_failed_to_configure; + if (!dev->interupt_read_loop(dev, ENDPOINT, PACKET_SIZE, &input_interrupt_cb, sc)) + DEBUG("New BLE Steam Controller with serial %s connected", sc->serial); + sc->state = SS_READY; + if (!daemon->controller_add(&sc->controller)) { + // This shouldn't happen unless memory is running out + DEBUG("Failed to add controller to daemon"); + goto hotplug_cb_fail; + } + return true; +hotplug_cb_failed_to_configure: + LERROR("Failed to configure controlller"); +hotplug_cb_fail: + if (sc != NULL) + free(sc); + dev->close(dev); + return true; +} + +static bool driver_start(Driver* drv, Daemon* daemon) { + HotplugFilter filter_vendor = { .type=SCCD_HOTPLUG_FILTER_VENDOR, .vendor=VENDOR_ID }; + HotplugFilter filter_product = { .type=SCCD_HOTPLUG_FILTER_PRODUCT, .product=PRODUCT_ID }; + HotplugFilter filter_idx = { .type=SCCD_HOTPLUG_FILTER_IDX, .idx=CONTROLIDX }; + Subsystem s = HIDAPI; +#if defined(_WIN32) + #define FILTERS &filter_vendor, &filter_product, &filter_idx +#elif defined(__BSD__) + #define FILTERS &filter_vendor, &filter_product, &filter_idx + s = UHID; +#else + #define FILTERS &filter_vendor, &filter_product +#endif + if (!daemon->hotplug_cb_add(s, hotplug_cb, FILTERS, NULL)) { + LERROR("Failed to register hotplug callback"); + return false; + } + return true; +} + +static void driver_list_devices(Driver* drv, Daemon* daemon, const controller_available_cb ca) { + controller_available = ca; + driver_start(drv, daemon); +} + +static Driver driver = { + .unload = NULL, + .start = driver_start, + // .list_devices = driver_list_devices, +}; + +Driver* scc_driver_init(Daemon* daemon) { + ASSERT(sizeof(TriggerValue) == 1); + ASSERT(sizeof(AxisValue) == 2); + ASSERT(sizeof(GyroValue) == 2); + // ^^ If any of above assertions fails, input_interrupt_cb code has to be + // modified so it doesn't use memcpy calls, as those depends on those sizes + + return &driver; +} + diff --git a/src/daemon/drivers/sc/common.c b/src/daemon/drivers/sc/common.c index f7ff90973..32f864a96 100644 --- a/src/daemon/drivers/sc/common.c +++ b/src/daemon/drivers/sc/common.c @@ -12,6 +12,7 @@ #define B_STICKTILT 0b10000000000000000000000000000000 #define BUFFER_SIZE 128 +#define SMALL_BUFFER_SIZE 64 static const char* get_description(Controller* c); static const char* get_type(Controller* c); @@ -27,31 +28,31 @@ static uint64_t used_serials = 0; void handle_input(SCController* sc, SCInput* i) { if (sc->mapper != NULL) { memcpy(&sc->input.ltrig, &i->ltrig, sizeof(TriggerValue) * 2); - memcpy(&sc->input.rpad_x, &i->rpad_x, sizeof(AxisValue) * 2); - memcpy(&sc->input.gyro, &i->accel_x, sizeof(struct GyroInput)); - - SCButton buttons = (((SCButton)i->buttons1) << 24) | (((SCButton)i->buttons0) << 8); - bool lpadtouch = buttons & B_LPADTOUCH; - bool sticktilt = buttons & B_STICKTILT; - if (lpadtouch & !sticktilt) - sc->input.stick_x = sc->input.stick_y = 0; - else if (!lpadtouch) - memcpy(&sc->input.stick_x, &i->lpad_x, sizeof(AxisValue) * 2); - if (!(lpadtouch || sticktilt)) - sc->input.lpad_x = sc->input.lpad_y = 0; - else if (lpadtouch) - memcpy(&sc->input.lpad_x, &i->lpad_x, sizeof(AxisValue) * 2); - - if (buttons & B_LPADPRESS) { - // LPADPRESS button may signalize pressing stick instead - if ((buttons & B_STICKPRESS) && !(buttons & B_STICKTILT)) - buttons &= ~B_LPADPRESS; - } - // Steam controller computes and sends dpad "buttons" as well, but - // SC Controller doesn't use them, so they are zeroed here - buttons &= ~0b00000000000011110000000000000000; - sc->input.buttons = buttons; - sc->mapper->input(sc->mapper, &sc->input); + memcpy(&sc->input.rpad_x, &i->rpad_x, sizeof(AxisValue) * 2); + memcpy(&sc->input.gyro, &i->accel_x, sizeof(struct GyroInput)); + + SCButton buttons = (((SCButton)i->buttons1) << 24) | (((SCButton)i->buttons0) << 8); + bool lpadtouch = buttons & B_LPADTOUCH; + bool sticktilt = buttons & B_STICKTILT; + if (lpadtouch & !sticktilt) + sc->input.stick_x = sc->input.stick_y = 0; + else if (!lpadtouch) + memcpy(&sc->input.stick_x, &i->lpad_x, sizeof(AxisValue) * 2); + if (!(lpadtouch || sticktilt)) + sc->input.lpad_x = sc->input.lpad_y = 0; + else if (lpadtouch) + memcpy(&sc->input.lpad_x, &i->lpad_x, sizeof(AxisValue) * 2); + + if (buttons & B_LPADPRESS) { + // LPADPRESS button may signalize pressing stick instead + if ((buttons & B_STICKPRESS) && !(buttons & B_STICKTILT)) + buttons &= ~B_LPADPRESS; + } + // Steam controller computes and sends dpad "buttons" as well, but + // SC Controller doesn't use them, so they are zeroed here + buttons &= ~0b00000000000011110000000000000000; + sc->input.buttons = buttons; + sc->mapper->input(sc->mapper, &sc->input); } } @@ -111,6 +112,7 @@ SCController* create_usb_controller(Daemon* daemon, InputDevice* dev, SCControll sc->idle_timeout = 10 * 60; // 10 minutes sc->led_level = 50; sc->type = type; + sc->long_packet = 0; sc->idx = idx; return sc; } @@ -172,6 +174,7 @@ static union { uint16_t period; uint16_t cunt; }; +//TODO need change for BT? } haptic = { .packet_type = PT_FEEDBACK, .len = PL_FEEDBACK, .cunt = 1 }; /** @@ -189,6 +192,7 @@ static void flush(Controller* c, Mapper* m) { // Special case, windows needs to do this synchronously // using hid_request, or it throws "overlapped operation in progress" // error. + //TODO need change for BT? haptic.packet_type = PT_FEEDBACK; haptic.len = PL_FEEDBACK; haptic.cunt = 1; @@ -219,6 +223,12 @@ static void update_desc(SCController* sc) { } } +static inline void debug_packet(char* buffer, size_t size) { + size_t i; + for(i=0; itype == SC_BT ? BUFFER_SIZE : (SMALL_BUFFER_SIZE + 1); + int bt_offset = 0; uint8_t* response; - response = sc->dev->hid_request(sc->dev, sc->idx, data, -64); + uint8_t* data; + if(sc->type == SC_BT){ + data = calloc(data_size, sizeof(uint8_t)); + data[0] = PT_BT_PREFIX; + data[1] = PT_GET_SERIAL; + data[2] = PL_GET_SERIAL; + data[3] = 0x01; + bt_offset = 2; + } else { + //TODO expand to 128 okay? + data = calloc(data_size, sizeof(uint8_t)); + data[0] = PT_GET_SERIAL; + data[1] = PL_GET_SERIAL; + data[2] = 0x01; + } + + if(sc->type == SC_BT){ + response = sc->dev->hid_request(sc->dev, sc->idx, data, -(SMALL_BUFFER_SIZE + 1)); + } else { + response = sc->dev->hid_request(sc->dev, sc->idx, data, -SMALL_BUFFER_SIZE); + } if (response == NULL) { LERROR("Failed to retrieve serial number"); return false; } - if ((data[0] != PT_GET_SERIAL) && (data[1] != PL_GET_SERIAL)) { + if (sc->type != SC_BT && (data[0] != PT_GET_SERIAL) && (data[1] != PL_GET_SERIAL)) { // Sometimes, freshly connected controller is not able to send its own // serial straight away return false; + } + if (sc->type == SC_BT && data[4] == 0) { + //TODO flush so don't have to reset controller + debug_packet((char *)data, data_size); + return false; } - data[13] = 0; // to terminate string - strncpy(sc->serial, (const char*) &data[3], MAX_SERIAL_LEN); + data[13 + bt_offset] = 0; // to terminate string + debug_packet((char *)data, data_size); + strncpy(sc->serial, (const char*) &data[3 + bt_offset], MAX_SERIAL_LEN); if (sc->type == SC_DECK) snprintf(sc->id, MAX_ID_LEN, "deck%s", sc->serial); else @@ -282,16 +320,39 @@ bool lizard_mode(SCController* sc) { } bool clear_mappings(SCController* sc) { - uint8_t data[BUFFER_SIZE] = { PT_CLEAR_MAPPINGS, 0x01 }; - - if (sc->dev->hid_request(sc->dev, sc->idx, data, -64) == NULL) { - LERROR("Failed to clear mappings"); - return false; + uint8_t* data; + int data_size = sc->type == SC_BT ? BUFFER_SIZE : (SMALL_BUFFER_SIZE + 1); + int bt_offset = 0; + if(sc->type == SC_BT){ + data = calloc(data_size, sizeof(uint8_t)); + data[0] = PT_BT_PREFIX; + data[1] = PT_CLEAR_MAPPINGS; + data[2] = 0x01; + bt_offset = 2; + } else { + //TODO expand to 128 okay? + data = calloc(data_size, sizeof(uint8_t)); + data[0] = PT_CLEAR_MAPPINGS; + data[1] = 0x01; + } + + if(sc->type == SC_BT){ + if (sc->dev->hid_request(sc->dev, sc->idx, data, -(SMALL_BUFFER_SIZE+1)) == NULL){ + LERROR("Failed to clear mappings"); + return false; + } + } else { + if (sc->dev->hid_request(sc->dev, sc->idx, data, -SMALL_BUFFER_SIZE) == NULL){ + LERROR("Failed to clear mappings"); + return false; + } } return true; } bool configure(SCController* sc) { + int bt_offset = 0; + int data_size = sc->type == SC_BT ? BUFFER_SIZE : (SMALL_BUFFER_SIZE + 1); if (sc->dev == NULL) // Special case, controller was disconnected, but it's not deallocated yet goto configure_fail; @@ -299,6 +360,30 @@ bool configure(SCController* sc) { uint8_t deck_configure[BUFFER_SIZE] = { PT_CONFIGURE, 0x03, 0x08, 0x07 }; if (sc->dev->hid_request(sc->dev, sc->idx, deck_configure, -64) == NULL) goto configure_fail; + } else if (sc->type == SC_BT) { + DDEBUG("configuring BT"); + + uint8_t gyro_and_timeout[SMALL_BUFFER_SIZE + 1] = { + // Header + // 0x87 0x0f 0x18 + PT_BT_PREFIX, PT_CONFIGURE, PL_CONFIGURE_BT, CONFIGURE_BT, + // Idle timeout + //TODO write IDLE timeout(?) + //(uint8_t)(sc->idle_timeout & 0xFF), (uint8_t)((sc->idle_timeout & 0xFF00) >> 8), + // unknown1 + 0x00, 0x00, 0x31, 0x02, 0x00, 0x08, 0x07, 0x00, 0x07, 0x07, 0x00, 0x30, + // Gyros + (sc->gyro_enabled ? 0x14 : 0), + // unknown2: + 0x00, 0x2e, + }; + uint8_t leds[65] = { PT_BT_PREFIX, PT_CONFIGURE, PL_LED, CT_LED, sc->led_level }; + if (sc->dev->hid_request(sc->dev, sc->idx, gyro_and_timeout, -(SMALL_BUFFER_SIZE + 1)) == NULL) + goto configure_fail; + if (sc->dev->hid_request(sc->dev, sc->idx, leds, -(SMALL_BUFFER_SIZE + 1)) == NULL) + goto configure_fail; + + bt_offset = 2; } else { uint8_t leds[BUFFER_SIZE] = { PT_CONFIGURE, PL_LED, CT_LED, sc->led_level }; uint8_t gyro_and_timeout[BUFFER_SIZE] = { diff --git a/src/daemon/drivers/sc/sc.h b/src/daemon/drivers/sc/sc.h index d0b294b36..ff76b7019 100644 --- a/src/daemon/drivers/sc/sc.h +++ b/src/daemon/drivers/sc/sc.h @@ -3,6 +3,7 @@ typedef struct SCController SCController; typedef struct SCInput SCInput; +typedef struct SCByBtControllerInput SCByBtControllerInput; typedef enum { SC_WIRED = 1, @@ -51,6 +52,76 @@ struct SCInput { // uint8_t _a4[16]; }; +struct SCByBtControllerInput { + uint16_t type; + uint32_t buttons; + uint8_t ltrig; + uint8_t rtrig; + int32_t stick_x; + int32_t stick_y; + int32_t lpad_x; + int32_t lpad_y; + int32_t rpad_x; + int32_t rpad_y; + int32_t gpitch; + int32_t groll; + int32_t gyaw; + int32_t q1; + int32_t q2; + int32_t q3; + int32_t q4; +}; + +enum SCButtons { + // This may be moved later to something shared + SCB_RPADTOUCH = 0b10000000000000000000000000000, + SCB_LPADTOUCH = 0b01000000000000000000000000000, + SCB_RPAD = 0b00100000000000000000000000000, + SCB_LPAD = 0b00010000000000000000000000000, // # Same for stick but without LPadTouch + SCB_STICKPRESS = 0b00000000000000000000000000001, // # generated internally, not sent by controller + SCB_RGRIP = 0b00001000000000000000000000000, + SCB_LGRIP = 0b00000100000000000000000000000, + SCB_START = 0b00000010000000000000000000000, + SCB_C = 0b00000001000000000000000000000, + SCB_BACK = 0b00000000100000000000000000000, + SCB_A = 0b00000000000001000000000000000, + SCB_X = 0b00000000000000100000000000000, + SCB_B = 0b00000000000000010000000000000, + SCB_Y = 0b00000000000000001000000000000, + SCB_LB = 0b00000000000000000100000000000, + SCB_RB = 0b00000000000000000010000000000, + SCB_LT = 0b00000000000000000001000000000, + SCB_RT = 0b00000000000000000000100000000, + SCB_CPADTOUCH = 0b00000000000000000000000000100, // # Available on DS4 pad + SCB_CPADPRESS = 0b00000000000000000000000000010, // # Available on DS4 pad +}; + +static uint32_t BT_BUTTONS[] = { + // Bit to SCButton + SCB_RT, // 00 + SCB_LT, // 01 + SCB_RB, // 02 + SCB_LB, // 03 + SCB_Y, // 04 + SCB_B, // 05 + SCB_X, // 06 + SCB_A, // 07 + 0, // 08 - dpad, ignored + 0, // 09 - dpad, ignored + 0, // 10 - dpad, ignored + 0, // 11 - dpad, ignored + SCB_BACK, // 12 + SCB_C, // 13 + SCB_START, // 14 + SCB_LGRIP, // 15 + SCB_RGRIP, // 16 + SCB_LPAD, // 17 + SCB_RPAD, // 18 + SCB_LPADTOUCH, // 19 + SCB_RPADTOUCH, // 20 + 0, // 21 - nothing + SCB_STICKPRESS, // 22 +}; struct SCController { Controller controller; @@ -59,10 +130,11 @@ struct SCController { SCControllerType type; SCControllerState state; InputDevice* dev; + bool long_packet; char serial[MAX_SERIAL_LEN]; char desc[MAX_DESC_LEN]; char id[MAX_ID_LEN]; - uint16_t idx; + int16_t idx; bool gyro_enabled; uint64_t auto_id; bool auto_id_used; @@ -89,6 +161,7 @@ typedef enum { PT_FEEDBACK = 0x8f, PT_RESET = 0x95, PT_GET_SERIAL = 0xAE, + PT_BT_PREFIX = 0xC0 } SCPacketType; typedef enum { diff --git a/src/daemon/input_bsd.c b/src/daemon/input_bsd.c index bf9a72303..f730fc069 100644 --- a/src/daemon/input_bsd.c +++ b/src/daemon/input_bsd.c @@ -62,7 +62,7 @@ static void sccd_input_bsd_hid_write(InputDevice* _dev, uint16_t idx, uint8_t* d WARN("sccd_usb_dev_hid_write: USB_SET_REPORT: %s", strerror(err)); } -static uint8_t* sccd_input_bsd_hid_request(InputDevice* _dev, uint16_t idx, uint8_t* data, int32_t _length) { +static uint8_t* sccd_input_bsd_hid_request(InputDevice* _dev, int16_t idx, uint8_t* data, int32_t _length) { BSDInputDevice* dev = container_of(_dev, BSDInputDevice, dev); uint16_t length; uint8_t* out_buffer = NULL; diff --git a/src/daemon/input_hidapi.c b/src/daemon/input_hidapi.c index 2edefa091..5ebb498d4 100644 --- a/src/daemon/input_hidapi.c +++ b/src/daemon/input_hidapi.c @@ -18,6 +18,7 @@ #include #define BUFFER_MAX 256 +#define SMALL_BUFFER_MAX 64 #define DEV_TYPECHECK(dev, method_name, err_return) do { \ if (dev->sys != HIDAPI) { \ LERROR("" #method_name " called on device of subsystem %i", dev->sys); \ @@ -93,7 +94,7 @@ static void sccd_input_hidapi_hid_write(InputDevice* _dev, uint16_t idx, uint8_t hid_write(dev->hid, data, length); } -static uint8_t* sccd_input_hidapi_hid_request(InputDevice* _dev, uint16_t idx, uint8_t* data, int32_t _length) { +static uint8_t* sccd_input_hidapi_hid_request(InputDevice* _dev, int16_t idx, uint8_t* data, int32_t _length) { DEV_TYPECHECK(_dev, sccd_input_hidapi_dev_close, NULL); HidapiInputDevice* dev = container_of(_dev, HidapiInputDevice, dev); @@ -114,15 +115,23 @@ static uint8_t* sccd_input_hidapi_hid_request(InputDevice* _dev, uint16_t idx, u } } - unsigned char buffer[BUFFER_MAX + 1]; - if (length > BUFFER_MAX) { - LERROR("sccd_input_hid_request: called with length larger" - "than supported. Changing BUFFER_MAX will fix this issue"); + int ACTUAL_BUFFER = BUFFER_MAX; + if(idx == -42){ + ACTUAL_BUFFER = SMALL_BUFFER_MAX; + } + unsigned char buffer[ACTUAL_BUFFER + 1]; + + //NEF TODO + if (length > ACTUAL_BUFFER+1) { + LERROR("sccd_input_hid_request: called with length larger %u > %d" + "than supported. Changing BUFFER_MAX will fix this issue", length, ACTUAL_BUFFER+1); return NULL; } - buffer[0] = 0; + + //TODO dont rely on IDX -1 quirk for BT report ID, test other controllers + buffer[0] = (idx == -1 ? 0x03 : 0x00); memcpy(&buffer[1], data, length); - if (dev->idx != idx) { + if (dev->idx != -42 && dev->idx != idx) { LERROR("sccd_input_hid_request: trying to send request to " "different idx than device was originally opened for (%i != %i)", dev->idx, idx); @@ -130,13 +139,13 @@ static uint8_t* sccd_input_hidapi_hid_request(InputDevice* _dev, uint16_t idx, u } err = hid_send_feature_report(dev->hid, buffer, length + 1); if (err < 0) { - wcstombs((char*)buffer, hid_error(dev->hid), BUFFER_MAX); + wcstombs((char*)buffer, hid_error(dev->hid), ACTUAL_BUFFER); LERROR("sccd_input_hid_request: hid_send_feature_report failed: %s", buffer); goto sccd_input_hid_request_fail; } err = hid_get_feature_report(dev->hid, buffer, length + 1); if (err < 0) { - wcstombs((char*)buffer, hid_error(dev->hid), BUFFER_MAX); + wcstombs((char*)buffer, hid_error(dev->hid), ACTUAL_BUFFER); LERROR("sccd_input_hid_request: hid_get_feautre_report failed: %s", buffer); goto sccd_input_hid_request_fail; } @@ -207,6 +216,7 @@ InputDevice* sccd_input_hidapi_open(const char* syspath) { free(dev); return NULL; } + DDEBUG("successfully opened device %s", device_path); dev->hid = hid; *((Subsystem*)(&dev->dev.sys)) = HIDAPI; @@ -226,9 +236,16 @@ InputDevice* sccd_input_hidapi_open(const char* syspath) { char* endptr = NULL; dev->idx = strtol(hex_str, &endptr, 16); if (endptr == hex_str) { - // Parsing failed + // Parsing failed dev->idx = -1; } + // TODO Add more criteria to matching + char* rev_component = strstr(device_path, "rev&0001"); + char* unknown_component = strstr(device_path, "0&0002"); + if(rev_component && unknown_component){ + //most likely a SC + dev->idx = -42; + } } #endif return &dev->dev; @@ -253,6 +270,7 @@ void sccd_input_hidapi_rescan() { wdev.product = dev->product_id; wdev.vendor = dev->vendor_id; wdev.idx = dev->interface_number; + DDEBUG("%u %u", dev->usage, dev->usage_page); sccd_device_monitor_new_device(get_daemon(), &wdev.idev); #endif dev = dev->next; diff --git a/src/daemon/input_libusb.c b/src/daemon/input_libusb.c index f82c97b69..281f6df6e 100644 --- a/src/daemon/input_libusb.c +++ b/src/daemon/input_libusb.c @@ -189,7 +189,7 @@ static void sccd_input_libusb_hid_write(InputDevice* _dev, uint16_t idx, uint8_t LERROR("sccd_input_libusb_hid_write: out: %s", libusb_strerror(err)); } -static uint8_t* sccd_input_libusb_hid_request(InputDevice* _dev, uint16_t idx, uint8_t* data, int32_t _length) { +static uint8_t* sccd_input_libusb_hid_request(InputDevice* _dev, int16_t idx, uint8_t* data, int32_t _length) { DEV_TYPECHECK(_dev, sccd_input_libusb_hid_request, NULL); USBInputDevice* dev = container_of(_dev, USBInputDevice, dev); From 9aa2bb82d60160c049523e8880fc6e6b3350517f Mon Sep 17 00:00:00 2001 From: unknown <38817597+pattontim@users.noreply.github.com> Date: Wed, 9 Nov 2022 20:11:32 -0500 Subject: [PATCH 2/3] Apply BT ephemeral bit only on Windows --- src/daemon/drivers/sc/by_bt.c | 2 +- src/daemon/drivers/sc/common.c | 37 +++++++++++++++++++++++++++++++--- src/daemon/input_hidapi.c | 12 +++++++++++ 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/src/daemon/drivers/sc/by_bt.c b/src/daemon/drivers/sc/by_bt.c index 351dd7bc8..78da3da3e 100644 --- a/src/daemon/drivers/sc/by_bt.c +++ b/src/daemon/drivers/sc/by_bt.c @@ -2,7 +2,7 @@ * Steam Controller Controller Steam Controller Driver * * Used to communicate with single Steam Controller - * connected directly by USB cable. + * connected via bluetooth. */ #define LOG_TAG "sc_by_bt" #include "scc/utils/logging.h" diff --git a/src/daemon/drivers/sc/common.c b/src/daemon/drivers/sc/common.c index 32f864a96..344cc5039 100644 --- a/src/daemon/drivers/sc/common.c +++ b/src/daemon/drivers/sc/common.c @@ -12,7 +12,7 @@ #define B_STICKTILT 0b10000000000000000000000000000000 #define BUFFER_SIZE 128 -#define SMALL_BUFFER_SIZE 64 +#define SMALL_BUFFER_SIZE 64 static const char* get_description(Controller* c); static const char* get_type(Controller* c); @@ -254,8 +254,12 @@ bool read_serial(SCController* sc) { } RC_REL(c); - //TODO +1 on windows only +//ephemeral extra bit at end (?) of buffer on windows only +#ifdef _WIN32 int data_size = sc->type == SC_BT ? BUFFER_SIZE : (SMALL_BUFFER_SIZE + 1); +#else + int data_size = sc->type == SC_BT ? BUFFER_SIZE : (SMALL_BUFFER_SIZE); +#endif int bt_offset = 0; uint8_t* response; uint8_t* data; @@ -275,7 +279,11 @@ bool read_serial(SCController* sc) { } if(sc->type == SC_BT){ +#ifdef _WIN32 response = sc->dev->hid_request(sc->dev, sc->idx, data, -(SMALL_BUFFER_SIZE + 1)); +#else + response = sc->dev->hid_request(sc->dev, sc->idx, data, -(SMALL_BUFFER_SIZE)); +#endif } else { response = sc->dev->hid_request(sc->dev, sc->idx, data, -SMALL_BUFFER_SIZE); } @@ -321,7 +329,11 @@ bool lizard_mode(SCController* sc) { bool clear_mappings(SCController* sc) { uint8_t* data; +#ifdef _WIN32 int data_size = sc->type == SC_BT ? BUFFER_SIZE : (SMALL_BUFFER_SIZE + 1); +#else + int data_size = sc->type == SC_BT ? BUFFER_SIZE : (SMALL_BUFFER_SIZE); +#endif int bt_offset = 0; if(sc->type == SC_BT){ data = calloc(data_size, sizeof(uint8_t)); @@ -337,7 +349,11 @@ bool clear_mappings(SCController* sc) { } if(sc->type == SC_BT){ +#ifdef _WIN32 if (sc->dev->hid_request(sc->dev, sc->idx, data, -(SMALL_BUFFER_SIZE+1)) == NULL){ +#else + if (sc->dev->hid_request(sc->dev, sc->idx, data, -(SMALL_BUFFER_SIZE)) == NULL){ +#endif LERROR("Failed to clear mappings"); return false; } @@ -352,7 +368,12 @@ bool clear_mappings(SCController* sc) { bool configure(SCController* sc) { int bt_offset = 0; +#ifdef _WIN32 int data_size = sc->type == SC_BT ? BUFFER_SIZE : (SMALL_BUFFER_SIZE + 1); +#else + int data_size = sc->type == SC_BT ? BUFFER_SIZE : (SMALL_BUFFER_SIZE); +#endif + if (sc->dev == NULL) // Special case, controller was disconnected, but it's not deallocated yet goto configure_fail; @@ -363,7 +384,11 @@ bool configure(SCController* sc) { } else if (sc->type == SC_BT) { DDEBUG("configuring BT"); +#ifdef _WIN32 uint8_t gyro_and_timeout[SMALL_BUFFER_SIZE + 1] = { +#else + uint8_t gyro_and_timeout[SMALL_BUFFER_SIZE] = { +#endif // Header // 0x87 0x0f 0x18 PT_BT_PREFIX, PT_CONFIGURE, PL_CONFIGURE_BT, CONFIGURE_BT, @@ -378,11 +403,17 @@ bool configure(SCController* sc) { 0x00, 0x2e, }; uint8_t leds[65] = { PT_BT_PREFIX, PT_CONFIGURE, PL_LED, CT_LED, sc->led_level }; +#ifdef _WIN32 if (sc->dev->hid_request(sc->dev, sc->idx, gyro_and_timeout, -(SMALL_BUFFER_SIZE + 1)) == NULL) goto configure_fail; if (sc->dev->hid_request(sc->dev, sc->idx, leds, -(SMALL_BUFFER_SIZE + 1)) == NULL) goto configure_fail; - +#else + if (sc->dev->hid_request(sc->dev, sc->idx, gyro_and_timeout, -(SMALL_BUFFER_SIZE)) == NULL) + goto configure_fail; + if (sc->dev->hid_request(sc->dev, sc->idx, leds, -(SMALL_BUFFER_SIZE)) == NULL) + goto configure_fail; +#endif bt_offset = 2; } else { uint8_t leds[BUFFER_SIZE] = { PT_CONFIGURE, PL_LED, CT_LED, sc->led_level }; diff --git a/src/daemon/input_hidapi.c b/src/daemon/input_hidapi.c index 5ebb498d4..72b6c8bbc 100644 --- a/src/daemon/input_hidapi.c +++ b/src/daemon/input_hidapi.c @@ -115,10 +115,13 @@ static uint8_t* sccd_input_hidapi_hid_request(InputDevice* _dev, int16_t idx, ui } } + //BT buffer int ACTUAL_BUFFER = BUFFER_MAX; if(idx == -42){ ACTUAL_BUFFER = SMALL_BUFFER_MAX; } + +#ifdef _WIN32 unsigned char buffer[ACTUAL_BUFFER + 1]; //NEF TODO @@ -127,6 +130,15 @@ static uint8_t* sccd_input_hidapi_hid_request(InputDevice* _dev, int16_t idx, ui "than supported. Changing BUFFER_MAX will fix this issue", length, ACTUAL_BUFFER+1); return NULL; } +#else + unsigned char buffer[ACTUAL_BUFFER]; + //NEF TODO + if (length > ACTUAL_BUFFER) { + LERROR("sccd_input_hid_request: called with length larger %u > %d" + "than supported. Changing BUFFER_MAX will fix this issue", length, ACTUAL_BUFFER); + return NULL; + } +#endif //TODO dont rely on IDX -1 quirk for BT report ID, test other controllers buffer[0] = (idx == -1 ? 0x03 : 0x00); From 6a21069421393299701303440948f43c384b995e Mon Sep 17 00:00:00 2001 From: Tim Patton <38817597+pattontim@users.noreply.github.com> Date: Thu, 2 Mar 2023 12:43:14 -0500 Subject: [PATCH 3/3] Hotfix spam inputs by disabling gyro on BT --- src/daemon/drivers/sc/common.c | 81 ++++++++++++++++++++++------------ 1 file changed, 53 insertions(+), 28 deletions(-) diff --git a/src/daemon/drivers/sc/common.c b/src/daemon/drivers/sc/common.c index 344cc5039..a44ffc711 100644 --- a/src/daemon/drivers/sc/common.c +++ b/src/daemon/drivers/sc/common.c @@ -28,31 +28,31 @@ static uint64_t used_serials = 0; void handle_input(SCController* sc, SCInput* i) { if (sc->mapper != NULL) { memcpy(&sc->input.ltrig, &i->ltrig, sizeof(TriggerValue) * 2); - memcpy(&sc->input.rpad_x, &i->rpad_x, sizeof(AxisValue) * 2); - memcpy(&sc->input.gyro, &i->accel_x, sizeof(struct GyroInput)); - - SCButton buttons = (((SCButton)i->buttons1) << 24) | (((SCButton)i->buttons0) << 8); - bool lpadtouch = buttons & B_LPADTOUCH; - bool sticktilt = buttons & B_STICKTILT; - if (lpadtouch & !sticktilt) - sc->input.stick_x = sc->input.stick_y = 0; - else if (!lpadtouch) - memcpy(&sc->input.stick_x, &i->lpad_x, sizeof(AxisValue) * 2); - if (!(lpadtouch || sticktilt)) - sc->input.lpad_x = sc->input.lpad_y = 0; - else if (lpadtouch) - memcpy(&sc->input.lpad_x, &i->lpad_x, sizeof(AxisValue) * 2); - - if (buttons & B_LPADPRESS) { - // LPADPRESS button may signalize pressing stick instead - if ((buttons & B_STICKPRESS) && !(buttons & B_STICKTILT)) - buttons &= ~B_LPADPRESS; - } - // Steam controller computes and sends dpad "buttons" as well, but - // SC Controller doesn't use them, so they are zeroed here - buttons &= ~0b00000000000011110000000000000000; - sc->input.buttons = buttons; - sc->mapper->input(sc->mapper, &sc->input); + memcpy(&sc->input.rpad_x, &i->rpad_x, sizeof(AxisValue) * 2); + memcpy(&sc->input.gyro, &i->accel_x, sizeof(struct GyroInput)); + + SCButton buttons = (((SCButton)i->buttons1) << 24) | (((SCButton)i->buttons0) << 8); + bool lpadtouch = buttons & B_LPADTOUCH; + bool sticktilt = buttons & B_STICKTILT; + if (lpadtouch & !sticktilt) + sc->input.stick_x = sc->input.stick_y = 0; + else if (!lpadtouch) + memcpy(&sc->input.stick_x, &i->lpad_x, sizeof(AxisValue) * 2); + if (!(lpadtouch || sticktilt)) + sc->input.lpad_x = sc->input.lpad_y = 0; + else if (lpadtouch) + memcpy(&sc->input.lpad_x, &i->lpad_x, sizeof(AxisValue) * 2); + + if (buttons & B_LPADPRESS) { + // LPADPRESS button may signalize pressing stick instead + if ((buttons & B_STICKPRESS) && !(buttons & B_STICKTILT)) + buttons &= ~B_LPADPRESS; + } + // Steam controller computes and sends dpad "buttons" as well, but + // SC Controller doesn't use them, so they are zeroed here + buttons &= ~0b00000000000011110000000000000000; + sc->input.buttons = buttons; + sc->mapper->input(sc->mapper, &sc->input); } } @@ -100,12 +100,14 @@ SCController* create_usb_controller(Daemon* daemon, InputDevice* dev, SCControll sc->controller.get_gyro_enabled = &get_gyro_enabled; // Main difference between dongle-bound and wired controller is that dongle-bound // countroller doesn't close USB device when deallocated - sc->controller.deallocate = (type == SC_WIRED) ? &deallocate : &deallocate_dongle_controller; + sc->controller.deallocate = (type == SC_WIRED || type == SC_BT) ? &deallocate : &deallocate_dongle_controller; HAPTIC_DISABLE(&sc->hdata[0]); sc->hdata[0].pos = HAPTIC_LEFT; HAPTIC_DISABLE(&sc->hdata[1]); sc->hdata[1].pos = HAPTIC_RIGHT; sc->state = SS_NOT_CONFIGURED; - sc->gyro_enabled = true; + // spam inputs as some cards drop packets when gyro packets are sent too fast + // TODO timesliced scheduler and poller system as in python version + sc->gyro_enabled = (type == SC_BT) ? false : true; sc->dev = dev; sc->daemon = daemon; sc->auto_id_used = false; @@ -205,6 +207,29 @@ static void flush(Controller* c, Mapper* m) { } } +// TODO does not work, find correct SC_BT command +static void turnoff(Controller* c) { + SCController* sc = container_of(c, SCController, controller); + DDEBUG("turn off req"); + switch (sc->type) { + case SC_WIRED: + break; + case SC_WIRELESS: + //0xC0, 0x9F, 0x04, 0x6f, 0x66, 0x66, 0x21 }; + uint8_t data1[] = { PT_OFF, PL_OFF, 0x6f, 0x66, 0x66, 0x21 }; + if (sc->dev->hid_request(sc->dev, sc->idx, data1, -(SMALL_BUFFER_SIZE)) == NULL) + LERROR("Failed to turn off controller"); + case SC_BT: + //0xC0, 0x9F, 0x04, 0x6f, 0x66, 0x66, 0x21 }; + uint8_t data[] = { PT_BT_PREFIX, PT_OFF, PL_OFF, 0x6f, 0x66, 0x66, 0x21 }; + if (sc->dev->hid_request(sc->dev, sc->idx, data, -(SMALL_BUFFER_SIZE + 1)) == NULL) + LERROR("Failed to turn off controller"); + break; + case SC_DECK: + break; + } +} + static void update_desc(SCController* sc) { switch (sc->type) { @@ -390,7 +415,7 @@ bool configure(SCController* sc) { uint8_t gyro_and_timeout[SMALL_BUFFER_SIZE] = { #endif // Header - // 0x87 0x0f 0x18 + // 0xc0 0x87 0x0f 0x18 PT_BT_PREFIX, PT_CONFIGURE, PL_CONFIGURE_BT, CONFIGURE_BT, // Idle timeout //TODO write IDLE timeout(?)