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

Adds RGB control feature #262

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions src/device.c
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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',
Expand Down
27 changes: 27 additions & 0 deletions src/device.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/devices/logitech_g430.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
53 changes: 49 additions & 4 deletions src/devices/logitech_g633_g933_935.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
Expand All @@ -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]));
Expand Down Expand Up @@ -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;
}
2 changes: 1 addition & 1 deletion src/devices/roccat_elo_7_1_air.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
63 changes: 62 additions & 1 deletion src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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;
Expand All @@ -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' },
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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, &notification_sound)) != 0)
goto error;
Expand Down