diff --git a/README.md b/README.md index e3c49f3..8588a88 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ talking. This differs from a simple loopback via PulseAudio as you won't have an - Logitech G535 - Sidetone (only tested on Linux) - Logitech G633 / G635 / G733 / G933 / G935 - - Sidetone, Battery (for Wireless), LED on/off + - Sidetone, Battery (for Wireless), LED on/off, RGB lights - Logitech G930 - Sidetone, Battery - SteelSeries Arctis 1, Arctis 1 for XBox @@ -151,6 +151,14 @@ The following options don't work on all devices yet: `headsetcontrol -l 0|1` switches LED off/on (off almost doubles battery lifetime!). +`headsetcontrol -R` Sets Red channel light color, values must be between 0 and 255 + +`headsetcontrol -G` Sets Green channel light color, values must be between 0 and 255 + +`headsetcontrol -B` Sets Blue channel light color, values must be between 0 and 255 + +`headsetcontrol -t 0|1` Sets top light color. Use it with -R -G -B. Default: Bottom (0 = bottom, 1 = top) + `headsetcontrol --short-output` cut unnecessary output, for reading by other scripts or applications. `headsetcontrol -i 0-90` sets inactive time in minutes, time must be between 0 and 90, 0 disables the feature. diff --git a/src/device.c b/src/device.c index 91dad97..31c4a71 100644 --- a/src/device.c +++ b/src/device.c @@ -6,6 +6,7 @@ const char* const capabilities_str[NUM_CAPABILITIES] [CAP_BATTERY_STATUS] = "battery status", [CAP_NOTIFICATION_SOUND] = "notification sound", [CAP_LIGHTS] = "lights", + [CAP_RGB] = "rgb lights", [CAP_INACTIVE_TIME] = "inactive time", [CAP_CHATMIX_STATUS] = "chatmix", [CAP_VOICE_PROMPTS] = "voice prompts", @@ -20,6 +21,7 @@ const char capabilities_str_short[NUM_CAPABILITIES] [CAP_BATTERY_STATUS] = 'b', [CAP_NOTIFICATION_SOUND] = 'n', [CAP_LIGHTS] = 'l', + [CAP_RGB] = 'L', [CAP_INACTIVE_TIME] = 'i', [CAP_CHATMIX_STATUS] = 'm', [CAP_VOICE_PROMPTS] = 'v', diff --git a/src/device.h b/src/device.h index 8243d65..e3c4458 100644 --- a/src/device.h +++ b/src/device.h @@ -24,6 +24,7 @@ enum capabilities { CAP_BATTERY_STATUS, CAP_NOTIFICATION_SOUND, CAP_LIGHTS, + CAP_RGB, CAP_INACTIVE_TIME, CAP_CHATMIX_STATUS, CAP_VOICE_PROMPTS, @@ -69,6 +70,17 @@ struct equalizer_settings { char bands_values[]; }; +/** @brief Defines RGB channels and light side + */ +struct rgb_settings { + /// The RGB channels values + uint8_t r_channel; + uint8_t g_channel; + uint8_t b_channel; + /// The top or bottom light + uint8_t top; +}; + /** @brief Defines the basic data of a device * * Also defines function pointers for using supported features @@ -144,6 +156,21 @@ struct device { */ int (*switch_lights)(hid_device* hid_device, uint8_t on); + /** @brief Function pointer for setting headset rgb light colors + * + * Forwards the request to the device specific implementation + * + * @param device_handle The hidapi handle. Must be the same + * device as defined here (same ids) + * @param rgb The RGB settings containing + * the channel values and light side + * + * @returns > 0 on success + * HSC_ERROR on error specific to this software + * -1 HIDAPI error + */ + int (*send_rgb)(hid_device* hid_device, struct rgb_settings* rgb); + /** @brief Function pointer for setting headset inactive time * * Forwards the request to the device specific implementation diff --git a/src/devices/logitech_g430.c b/src/devices/logitech_g430.c index 5568886..b90fd7f 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 64f0d5a..4acb441 100644 --- a/src/devices/logitech_g633_g933_935.c +++ b/src/devices/logitech_g633_g933_935.c @@ -20,23 +20,27 @@ static const uint16_t PRODUCT_IDS[] = { ID_LOGITECH_G633, ID_LOGITECH_G635, ID_L static int g933_935_send_sidetone(hid_device* device_handle, uint8_t num); static int g933_935_request_battery(hid_device* device_handle); static int g933_935_lights(hid_device* device_handle, uint8_t on); +static int g933_935_send_rgb(hid_device* device_handle, struct rgb_settings* rgb); +double color_equation(double x); void g933_935_init(struct device** device) { device_g933_935.idVendor = VENDOR_LOGITECH; device_g933_935.idProductsSupported = PRODUCT_IDS; device_g933_935.numIdProducts = sizeof(PRODUCT_IDS) / sizeof(PRODUCT_IDS[0]); - strncpy(device_g933_935.device_name, "Logitech G633/G635/G933/G935", sizeof(device_g933_935.device_name)); + strncpy(device_g933_935.device_name, "Logitech G633/G635/G733/G933/G935", sizeof(device_g933_935.device_name)); - device_g933_935.capabilities = B(CAP_SIDETONE) | B(CAP_BATTERY_STATUS) | B(CAP_LIGHTS); + device_g933_935.capabilities = B(CAP_SIDETONE) | B(CAP_BATTERY_STATUS) | B(CAP_LIGHTS) | B(CAP_RGB); /// TODO: usagepages and ids may not be correct for all features device_g933_935.capability_details[CAP_SIDETONE] = (struct capability_detail) { .usagepage = 0xff43, .usageid = 0x0202 }; device_g933_935.capability_details[CAP_BATTERY_STATUS] = (struct capability_detail) { .usagepage = 0xff43, .usageid = 0x0202 }; device_g933_935.capability_details[CAP_LIGHTS] = (struct capability_detail) { .usagepage = 0xff43, .usageid = 0x0202 }; + device_g933_935.capability_details[CAP_RGB] = (struct capability_detail) { .usagepage = 0xff43, .usageid = 0x0202 }; device_g933_935.send_sidetone = &g933_935_send_sidetone; device_g933_935.request_battery = &g933_935_request_battery; device_g933_935.switch_lights = &g933_935_lights; + device_g933_935.send_rgb = &g933_935_send_rgb; *device = &device_g933_935; } @@ -75,7 +79,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; @@ -102,7 +106,7 @@ static int g933_935_send_sidetone(hid_device* device_handle, uint8_t num) uint8_t data_send[HIDPP_LONG_MESSAGE_LENGTH] = { HIDPP_LONG_MESSAGE, HIDPP_DEVICE_RECEIVER, 0x07, 0x1a, num, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; #ifdef DEBUG - printf("G33 - setting sidetone to: %2x", num); + printf("G33 - setting sidetone to: %2x\n", num); #endif return hid_write(device_handle, data_send, sizeof(data_send) / sizeof(data_send[0])); @@ -130,3 +134,44 @@ static int g933_935_lights(hid_device* device_handle, uint8_t on) return res; } + +static int g933_935_send_rgb(hid_device* device_handle, struct rgb_settings* rgb) +{ + + int R_fit = (int)color_equation(rgb->r_channel); + int G_fit = (int)color_equation(rgb->g_channel); + int B_fit = (int)color_equation(rgb->b_channel); + + uint8_t data_send[HIDPP_LONG_MESSAGE_LENGTH] = { HIDPP_LONG_MESSAGE, HIDPP_DEVICE_RECEIVER, 0x04, 0x3e, rgb->top, 0x01, R_fit, G_fit, B_fit, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +#ifdef DEBUG + printf("G33 - setting RGB to: %d %d %d\n", R_fit, G_fit, B_fit); +#endif + + return hid_write(device_handle, data_send, sizeof(data_send) / sizeof(data_send[0])); +} + +double color_equation(double x) +{ + if (x > 255) + x = 255; + if (x < 0) + x = 0; + + double terms[] = { + 1.6819656928435833e-001, + -3.7521501680023439e-003, + 2.7707067841309251e-003, + 4.5469372940149370e-006 + }; + + size_t csz = sizeof terms / sizeof *terms; + + double t = 1; + double r = 0; + for (int i = 0; i < csz; i++) { + r += terms[i] * t; + t *= x; + } + return r; +} diff --git a/src/devices/roccat_elo_7_1_air.c b/src/devices/roccat_elo_7_1_air.c index d775caa..f6f24f3 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 } diff --git a/src/main.c b/src/main.c index 7d75f34..d71decc 100644 --- a/src/main.c +++ b/src/main.c @@ -204,6 +204,10 @@ static int handle_feature(struct device* device_found, hid_device** device_handl ret = device_found->switch_lights(*device_handle, *(int*)param); break; + case CAP_RGB: + ret = device_found->send_rgb(*device_handle, (struct rgb_settings*)param); + break; + case CAP_INACTIVE_TIME: ret = device_found->send_inactive_time(*device_handle, *(int*)param); @@ -289,6 +293,10 @@ int main(int argc, char* argv[]) int request_chatmix = 0; int notification_sound = -1; int lights = -1; + int r_channel = -1; + int g_channel = -1; + int b_channel = -1; + int top = 0; int inactive_time = -1; int voice_prompts = -1; int rotate_to_mute = -1; @@ -312,6 +320,10 @@ int main(int argc, char* argv[]) { "equalizer-preset", required_argument, NULL, 'p' }, { "inactive-time", required_argument, NULL, 'i' }, { "light", required_argument, NULL, 'l' }, + { "r_channel", required_argument, NULL, 'R' }, + { "g_channel", required_argument, NULL, 'G' }, + { "b_channel", required_argument, NULL, 'B' }, + { "top", optional_argument, NULL, 't' }, { "follow", optional_argument, NULL, 'f' }, { "notificate", required_argument, NULL, 'n' }, { "rotate-to-mute", required_argument, NULL, 'r' }, @@ -327,7 +339,7 @@ int main(int argc, char* argv[]) // Init all information of supported devices init_devices(); - while ((c = getopt_long(argc, argv, "bchi:l:f::mn:r:s:uv:p:e:?", opts, &option_index)) != -1) { + while ((c = getopt_long(argc, argv, "bchi:l:f::mn:r:s:uv:p:e:?R:G:B:t::", opts, &option_index)) != -1) { char* endptr = NULL; // for strtol switch (c) { @@ -382,6 +394,40 @@ int main(int argc, char* argv[]) return 1; } break; + case 'R': + r_channel = strtol(optarg, &endptr, 10); + + if (*endptr != '\0' || endptr == optarg || r_channel > 255 || r_channel < 0) { + printf("Usage: %s -R 0-255\n", argv[0]); + return 1; + } + break; + case 'G': + g_channel = strtol(optarg, &endptr, 10); + + if (*endptr != '\0' || endptr == optarg || g_channel > 255 || g_channel < 0) { + printf("Usage: %s -G 0-255\n", argv[0]); + return 1; + } + break; + case 'B': + b_channel = strtol(optarg, &endptr, 10); + + if (*endptr != '\0' || endptr == optarg || b_channel > 255 || b_channel < 0) { + printf("Usage: %s -B 0-255\n", argv[0]); + return 1; + } + break; + case 't': + top = 1; + if (OPTIONAL_ARGUMENT_IS_PRESENT) { + top = strtol(optarg, &endptr, 10); + if (top != 0 && top != 1) { + printf("Usage: %s -t 0|1\n", argv[0]); + return 1; + } + } + break; case 'm': request_chatmix = 1; break; @@ -437,6 +483,10 @@ int main(int argc, char* argv[]) printf(" -b, --battery\t\t\tChecks the battery level\n"); printf(" -n, --notificate soundid\tMakes the headset play a notifiation\n"); printf(" -l, --light 0|1\t\tSwitch lights (0 = off, 1 = on)\n"); + printf(" -R, --red\t\t\tSets Red channel light color, values must be between 0 and 255\n"); + printf(" -G, --green\t\t\tSets Green channel light color, values must be between 0 and 255\n"); + printf(" -B, --blue\t\t\tSets Blue channel light color, values must be between 0 and 255\n"); + printf(" -t, --top 0|1\t\t\tSets top light color. Use it with -R -G -B. Default: Bottom (0 = bottom, 1 = top)\n"); printf(" -c, --short-output\t\tUse more machine-friendly output \n"); printf(" -i, --inactive-time time\tSets inactive time in minutes, time must be between 0 and 90, 0 disables the feature.\n"); printf(" -m, --chatmix\t\t\tRetrieves the current chat-mix-dial level setting between 0 and 128. Below 64 is the game side and above is the chat side.\n"); @@ -511,6 +561,17 @@ int main(int argc, char* argv[]) goto error; } + if (r_channel != -1 || g_channel != -1 || b_channel != -1) { + struct rgb_settings rgb; + rgb.r_channel = (r_channel < 0) ? 0 : r_channel; + rgb.g_channel = (g_channel < 0) ? 0 : g_channel; + rgb.b_channel = (b_channel < 0) ? 0 : b_channel; + rgb.top = top; + + if ((error = handle_feature(&device_found, &device_handle, &hid_path, CAP_RGB, &rgb)) != 0) + goto error; + } + if (notification_sound != -1) { if ((error = handle_feature(&device_found, &device_handle, &hid_path, CAP_NOTIFICATION_SOUND, ¬ification_sound)) != 0) goto error;