diff --git a/README.md b/README.md index c9130d27..7dd3dac6 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ talking. This differs from a simple loopback via PulseAudio as you won't have an ## Supported Headsets +- HyperX Cloud Alpha Wireless + - Battery, Inactive time, Sidetone, Voice Prompts (only tested on Linux) - HyperX Cloud Flight Wireless - Battery only (only tested on Linux) - Corsair **Void** (Every void-version*, regardless whether Elite, Pro, HS70 Wireless) diff --git a/src/device_registry.c b/src/device_registry.c index d8cafe44..c3b07da9 100644 --- a/src/device_registry.c +++ b/src/device_registry.c @@ -1,6 +1,7 @@ #include "device_registry.h" #include "devices/corsair_void.h" +#include "devices/hyperx_calphaw.h" #include "devices/hyperx_cflight.h" #include "devices/logitech_g430.h" #include "devices/logitech_g432.h" @@ -21,7 +22,7 @@ #include -#define NUMDEVICES 18 +#define NUMDEVICES 19 // array of pointers to device static struct device*(devicelist[NUMDEVICES]); @@ -46,6 +47,7 @@ void init_devices() cflight_init(&devicelist[15]); g535_init(&devicelist[16]); arctis_nova_7_init(&devicelist[17]); + calphaw_init(&devicelist[18]); } int get_device(struct device* device_found, uint16_t idVendor, uint16_t idProduct) diff --git a/src/devices/CMakeLists.txt b/src/devices/CMakeLists.txt index ce5ed795..9db9ab25 100644 --- a/src/devices/CMakeLists.txt +++ b/src/devices/CMakeLists.txt @@ -1,6 +1,8 @@ set(SOURCE_FILES ${SOURCE_FILES} ${CMAKE_CURRENT_SOURCE_DIR}/hyperx_cflight.c ${CMAKE_CURRENT_SOURCE_DIR}/hyperx_cflight.h + ${CMAKE_CURRENT_SOURCE_DIR}/hyperx_calphaw.c + ${CMAKE_CURRENT_SOURCE_DIR}/hyperx_calphaw.h ${CMAKE_CURRENT_SOURCE_DIR}/corsair_void.c ${CMAKE_CURRENT_SOURCE_DIR}/corsair_void.h ${CMAKE_CURRENT_SOURCE_DIR}/logitech_g930.c diff --git a/src/devices/hyperx_calphaw.c b/src/devices/hyperx_calphaw.c new file mode 100644 index 00000000..2e7a2150 --- /dev/null +++ b/src/devices/hyperx_calphaw.c @@ -0,0 +1,160 @@ +// Based on hyperx_cflight.c +// Most requests taken off https://github.com/lehnification/hyperx-cloud-alpha-wireless-tray unless stated otherwise + +// This file is part of HeadsetControl. + +#include "../device.h" +#include "../utility.h" + +#include +#include +#include +#include +#include + +// #define DEBUG + +#ifdef DEBUG +#include +#endif + +static struct device device_calphaw; + +// timeout for hidapi requests in milliseconds +#define TIMEOUT 2000 + +#define VENDOR_HP 0x03f0 // cloud alpha sold with HP, Inc vendor id +#define ID_CALPHAW 0x098d + +static const uint16_t PRODUCT_IDS[] = { ID_CALPHAW }; + +static int calphaw_request_battery(hid_device* device_handle); +static int calphaw_send_inactive_time(hid_device* device_handle, uint8_t num); +static int calphaw_send_sidetone(hid_device* device_handle, uint8_t num); +static int calphaw_switch_voice_prompts(hid_device* device_handle, uint8_t on); + +void calphaw_init(struct device** device) +{ + device_calphaw.idVendor = VENDOR_HP; + device_calphaw.idProductsSupported = PRODUCT_IDS; + device_calphaw.numIdProducts = sizeof(PRODUCT_IDS) / sizeof(PRODUCT_IDS[0]); + strncpy(device_calphaw.device_name, "HyperX Cloud Alpha Wireless", sizeof(device_calphaw.device_name)); + + device_calphaw.capabilities = B(CAP_BATTERY_STATUS) | B(CAP_INACTIVE_TIME) | B(CAP_SIDETONE) | B(CAP_VOICE_PROMPTS); + device_calphaw.request_battery = &calphaw_request_battery; + device_calphaw.send_inactive_time = &calphaw_send_inactive_time; + device_calphaw.send_sidetone = &calphaw_send_sidetone; + device_calphaw.switch_voice_prompts = &calphaw_switch_voice_prompts; + + *device = &device_calphaw; +} + +int calphaw_request_charge(hid_device* device_handle) +{ + int r = 0; + // request charge info + uint8_t data_request[31] = { 0x21, 0xbb, 0x0c }; + + r = hid_write(device_handle, data_request, sizeof(data_request) / sizeof(data_request[0])); + if (r < 0) + return r; + + uint8_t data_read[31]; + r = hid_read_timeout(device_handle, data_read, 20, TIMEOUT); + if (r < 0) + return r; + if (r == 0) // timeout + return HSC_ERROR; + + if (r == 0xf || r == 0x14) { + if (data_read[3] == 0x01) + return 1; + + return 0; + } + + return HSC_ERROR; +} + +static int calphaw_request_battery(hid_device* device_handle) +{ + if (calphaw_request_charge(device_handle) == 1) + return BATTERY_CHARGING; + + int r = 0; + // request battery info + uint8_t data_request[31] = { 0x21, 0xbb, 0x0b }; // Data request reverse engineered by using USBPcap and Wireshark by @InfiniteLoop + + r = hid_write(device_handle, data_request, sizeof(data_request) / sizeof(data_request[0])); + if (r < 0) + return r; + + uint8_t data_read[31]; + r = hid_read_timeout(device_handle, data_read, 20, TIMEOUT); + if (r < 0) + return r; + if (r == 0) // timeout + return HSC_ERROR; + + if (r == 0xf || r == 0x14) { + +#ifdef DEBUG + printf("calphaw_request_battery data_read 3: 0x%02x 4: 0x%02x 5: 0x%02x\n", data_read[3], data_read[4], data_read[5]); +#endif + + uint8_t batteryPercentage = data_read[3]; + +#ifdef DEBUG + uint32_t batteryVoltage = (data_read[4] << 8) | data_read[5]; // best assumtion this is voltage in mV + printf("batteryVoltage: %d mV\n", batteryVoltage); +#endif + return (int)(batteryPercentage); + } + + return HSC_ERROR; +} + +static int calphaw_send_inactive_time(hid_device* device_handle, uint8_t num) +{ + // taken from steelseries_arctis_7.c + + uint8_t data_request[31] = { 0x21, 0xbb, 0x12, num }; + + int ret = hid_write(device_handle, data_request, sizeof(data_request) / sizeof(data_request[0])); + + return ret; +} + +int calphaw_toggle_sidetone(hid_device* device_handle, uint8_t num) +{ + uint8_t data_request[31] = { 0x21, 0xbb, 0x10, num }; + + return hid_write(device_handle, data_request, sizeof(data_request) / sizeof(data_request[0])); + ; +} + +static int calphaw_send_sidetone(hid_device* device_handle, uint8_t num) +{ + if (num == 0) { + return calphaw_toggle_sidetone(device_handle, 0); + } else { + calphaw_toggle_sidetone(device_handle, 1); + uint8_t data_request[31] = { 0x21, 0xbb, 0x11, num }; + + int ret = hid_write(device_handle, data_request, sizeof(data_request) / sizeof(data_request[0])); + + // looks to be a binary setting but I'm not sure + + return ret; + } + + return HSC_ERROR; +} + +static int calphaw_switch_voice_prompts(hid_device* device_handle, uint8_t on) +{ + uint8_t data_request[31] = { 0x21, 0xbb, 0x13, on }; + + return hid_write(device_handle, data_request, sizeof(data_request) / sizeof(data_request[0])); + ; +} \ No newline at end of file diff --git a/src/devices/hyperx_calphaw.h b/src/devices/hyperx_calphaw.h new file mode 100644 index 00000000..bdf4446a --- /dev/null +++ b/src/devices/hyperx_calphaw.h @@ -0,0 +1 @@ +void calphaw_init(struct device** device); diff --git a/src/devices/hyperx_cflight.c b/src/devices/hyperx_cflight.c index d5baa7ca..dcb8021b 100644 --- a/src/devices/hyperx_cflight.c +++ b/src/devices/hyperx_cflight.c @@ -13,7 +13,7 @@ #include #include -//#define DEBUG +// #define DEBUG #ifdef DEBUG #include diff --git a/src/devices/logitech_g430.c b/src/devices/logitech_g430.c index 5568886e..b90fd7fd 100644 --- a/src/devices/logitech_g430.c +++ b/src/devices/logitech_g430.c @@ -41,9 +41,9 @@ static int g430_send_sidetone(hid_device* device_handle, uint8_t num) 0x081E, 0x1E1E, 0x341E, 0x4A1E, 0x601E, 0x761E, 0x8C1E, 0xA21E, 0xB81E, 0xCE1E, 0xE41E, 0xFA1E, 0x101F, 0x261F, 0x3C1F, 0x521F, 0x681F };*/ - //unsigned char data[2] = { volumes[num] >> 8, volumes[num] & 0xFF }; + // unsigned char data[2] = { volumes[num] >> 8, volumes[num] & 0xFF }; - //int size = libusb_control_transfer(device_handle, LIBUSB_DT_HID, LIBUSB_REQUEST_CLEAR_FEATURE, 0x0201, 0x0600, data, 0x2, 1000); + // int size = libusb_control_transfer(device_handle, LIBUSB_DT_HID, LIBUSB_REQUEST_CLEAR_FEATURE, 0x0201, 0x0600, data, 0x2, 1000); // unused parameter (void)(device_handle); diff --git a/src/devices/logitech_g633_g933_935.c b/src/devices/logitech_g633_g933_935.c index 64f0d5ad..7a049424 100644 --- a/src/devices/logitech_g633_g933_935.c +++ b/src/devices/logitech_g633_g933_935.c @@ -75,7 +75,7 @@ static int g933_935_request_battery(hid_device* device_handle) if (r == 0) return HSC_READ_TIMEOUT; - //6th byte is state; 0x1 for idle, 0x3 for charging + // 6th byte is state; 0x1 for idle, 0x3 for charging uint8_t state = data_read[6]; if (state == 0x03) return BATTERY_CHARGING; diff --git a/src/devices/roccat_elo_7_1_air.c b/src/devices/roccat_elo_7_1_air.c index d775caa7..f6f24f38 100644 --- a/src/devices/roccat_elo_7_1_air.c +++ b/src/devices/roccat_elo_7_1_air.c @@ -113,5 +113,5 @@ static int elo71Air_send_inactive_time(hid_device* hid_device, uint8_t num) uint8_t response[64]; return send_receive(hid_device, cmd, sizeof(cmd), response); - //meaning of response of headset is not clear yet, fetch & ignore it for now + // meaning of response of headset is not clear yet, fetch & ignore it for now }