forked from drath/libratbag
973 lines
26 KiB
C
973 lines
26 KiB
C
/*
|
|
* Copyright © 2017 Thomas Hindoe Paaboel Andersen.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice (including the next
|
|
* paragraph) shall be included in all copies or substantial portions of the
|
|
* Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <libevdev/libevdev.h>
|
|
#include <linux/input.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#include "libratbag-private.h"
|
|
#include "libratbag-hidraw.h"
|
|
#include "libratbag-data.h"
|
|
#include "hidpp-generic.h"
|
|
|
|
#define STEELSERIES_NUM_PROFILES 1
|
|
#define STEELSERIES_NUM_DPI 2
|
|
#define STEELSERIES_INPUT_ENDPOINT 0
|
|
#define STEELSERIES_INPUT_HIDRAW 1
|
|
|
|
/* not sure these two are used for */
|
|
#define STEELSERIES_REPORT_ID_1 0x01
|
|
#define STEELSERIES_REPORT_ID_2 0x02
|
|
|
|
#define STEELSERIES_REPORT_SIZE_SHORT 32
|
|
#define STEELSERIES_REPORT_SIZE 64
|
|
#define STEELSERIES_REPORT_LONG_SIZE 262
|
|
|
|
#define STEELSERIES_ID_DPI_SHORT 0x03
|
|
#define STEELSERIES_ID_REPORT_RATE_SHORT 0x04
|
|
#define STEELSERIES_ID_LED_INTENSITY_SHORT 0x05
|
|
#define STEELSERIES_ID_LED_EFFECT_SHORT 0x07
|
|
#define STEELSERIES_ID_LED_COLOR_SHORT 0x08
|
|
#define STEELSERIES_ID_SAVE_SHORT 0x09
|
|
|
|
#define STEELSERIES_ID_BUTTONS 0x31
|
|
#define STEELSERIES_ID_DPI 0x53
|
|
#define STEELSERIES_ID_REPORT_RATE 0x54
|
|
#define STEELSERIES_ID_LED 0x5b
|
|
#define STEELSERIES_ID_SAVE 0x59
|
|
#define STEELSERIES_ID_FIRMWARE 0x90
|
|
#define STEELSERIES_ID_SETTTINGS 0x92
|
|
|
|
#define STEELSERIES_ID_DPI_PROTOCOL3 0x03
|
|
#define STEELSERIES_ID_REPORT_RATE_PROTOCOL3 0x04
|
|
#define STEELSERIES_ID_LED_PROTOCOL3 0x05
|
|
#define STEELSERIES_ID_SAVE_PROTOCOL3 0x09
|
|
#define STEELSERIES_ID_FIRMWARE_PROTOCOL3 0x10
|
|
#define STEELSERIES_ID_SETTTINGS_PROTOCOL3 0x16
|
|
|
|
#define STEELSERIES_BUTTON_OFF 0x00
|
|
#define STEELSERIES_BUTTON_RES_CYCLE 0x30
|
|
#define STEELSERIES_BUTTON_WHEEL_UP 0x31
|
|
#define STEELSERIES_BUTTON_WHEEL_DOWN 0x32
|
|
#define STEELSERIES_BUTTON_KEY 0x10
|
|
#define STEELSERIES_BUTTON_KBD 0x51
|
|
#define STEELSERIES_BUTTON_CONSUMER 0x61
|
|
|
|
struct steelseries_data {
|
|
int firmware_major;
|
|
int firmware_minor;
|
|
};
|
|
|
|
struct steelseries_point {
|
|
struct list link;
|
|
|
|
struct ratbag_color color; /* point color */
|
|
uint8_t pos; /* relative position in the cycle */
|
|
};
|
|
|
|
struct steelseries_led_cycle {
|
|
uint8_t led_id; /* led id */
|
|
uint16_t duration; /* cycle duration */
|
|
bool repeat; /* if the cycle restarts automatically */
|
|
uint8_t trigger_buttons; /* trigger button combination */
|
|
struct list points; /* colors in the cycle */
|
|
};
|
|
|
|
struct steelseries_led_cycle_spec {
|
|
int hid_report_type; /* either HID_OUTPUT_REPORT or HID_FEATURE_REPORT */
|
|
int header_len; /* number of bytes in the header */
|
|
uint8_t cmd_val; /* command value for the color command */
|
|
bool has_2_led_ids; /* some mice have 2 fields for led id */
|
|
|
|
int led_id_idx; /* index of the led id field */
|
|
int led_id2_idx; /* 2nd led id index (if required by protocol) */
|
|
int duration_idx; /* index of the cycle duration field */
|
|
int repeat_idx; /* index of the repeat field */
|
|
int trigger_idx; /* index of the trigger mask field */
|
|
int point_count_idx; /* index of the point counter field */
|
|
};
|
|
|
|
static int
|
|
steelseries_test_hidraw(struct ratbag_device *device)
|
|
{
|
|
int device_version = ratbag_device_data_steelseries_get_device_version(device->data);
|
|
|
|
if (device_version > 1)
|
|
return ratbag_hidraw_has_report(device, STEELSERIES_REPORT_ID_1);
|
|
else
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
button_defaults_for_layout(struct ratbag_button *button, int button_count)
|
|
{
|
|
/* The default button mapping vary depending on the number of buttons
|
|
* on the device. */
|
|
enum ratbag_button_type button_types[8] = {RATBAG_BUTTON_TYPE_UNKNOWN};
|
|
struct ratbag_button_action button_actions[8] = {
|
|
BUTTON_ACTION_BUTTON(1),
|
|
BUTTON_ACTION_BUTTON(2),
|
|
BUTTON_ACTION_BUTTON(3),
|
|
BUTTON_ACTION_BUTTON(4),
|
|
BUTTON_ACTION_BUTTON(5),
|
|
BUTTON_ACTION_BUTTON(6),
|
|
BUTTON_ACTION_BUTTON(7),
|
|
BUTTON_ACTION_BUTTON(8),
|
|
};
|
|
|
|
if (button_count <= 6) {
|
|
button_actions[5].type = RATBAG_BUTTON_ACTION_TYPE_SPECIAL;
|
|
button_actions[5].action.special = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP;
|
|
button_actions[6].type = RATBAG_BUTTON_ACTION_TYPE_NONE;
|
|
button_actions[7].type = RATBAG_BUTTON_ACTION_TYPE_NONE;
|
|
} else if (button_count == 7) {
|
|
button_actions[6].type = RATBAG_BUTTON_ACTION_TYPE_SPECIAL;
|
|
button_actions[6].action.special = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP;
|
|
button_actions[7].type = RATBAG_BUTTON_ACTION_TYPE_NONE;
|
|
} else {
|
|
button_actions[7].type = RATBAG_BUTTON_ACTION_TYPE_SPECIAL;
|
|
button_actions[7].action.special = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP;
|
|
}
|
|
|
|
button_types[0] = RATBAG_BUTTON_TYPE_LEFT;
|
|
button_types[1] = RATBAG_BUTTON_TYPE_RIGHT;
|
|
button_types[2] = RATBAG_BUTTON_TYPE_MIDDLE;
|
|
button_types[3] = RATBAG_BUTTON_TYPE_THUMB;
|
|
button_types[4] = RATBAG_BUTTON_TYPE_THUMB2;
|
|
if (button_count <= 6) {
|
|
button_types[5] = RATBAG_BUTTON_TYPE_RESOLUTION_CYCLE_UP;
|
|
} else if (button_count == 7) {
|
|
button_types[5] = RATBAG_BUTTON_TYPE_THUMB3;
|
|
button_types[6] = RATBAG_BUTTON_TYPE_RESOLUTION_CYCLE_UP;
|
|
} else {
|
|
button_types[5] = RATBAG_BUTTON_TYPE_PINKIE;
|
|
button_types[6] = RATBAG_BUTTON_TYPE_PINKIE2;
|
|
button_types[7] = RATBAG_BUTTON_TYPE_RESOLUTION_CYCLE_UP;
|
|
}
|
|
|
|
button->type = button_types[button->index];
|
|
ratbag_button_set_action(button, &button_actions[button->index]);
|
|
}
|
|
|
|
static int
|
|
steelseries_get_firmware_version(struct ratbag_device *device)
|
|
{
|
|
struct steelseries_data *drv_data = device->drv_data;
|
|
int device_version = ratbag_device_data_steelseries_get_device_version(device->data);
|
|
size_t buf_len = STEELSERIES_REPORT_SIZE;
|
|
uint8_t buf[STEELSERIES_REPORT_SIZE] = {0};
|
|
int ret;
|
|
|
|
if (device_version == 2)
|
|
buf[0] = STEELSERIES_ID_FIRMWARE;
|
|
else if (device_version == 3)
|
|
buf[0] = STEELSERIES_ID_FIRMWARE_PROTOCOL3;
|
|
else
|
|
return -ENOTSUP;
|
|
|
|
msleep(10);
|
|
ret = ratbag_hidraw_output_report(device, buf, buf_len);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = ratbag_hidraw_read_input_report_index(device, buf, buf_len, STEELSERIES_INPUT_HIDRAW);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
drv_data->firmware_major = buf[1];
|
|
drv_data->firmware_minor = buf[0];
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
steelseries_read_settings(struct ratbag_device *device)
|
|
{
|
|
int device_version = ratbag_device_data_steelseries_get_device_version(device->data);
|
|
struct ratbag_profile *profile = NULL;
|
|
struct ratbag_resolution *resolution;
|
|
struct ratbag_led *led;
|
|
|
|
size_t buf_len = STEELSERIES_REPORT_SIZE;
|
|
uint8_t buf[STEELSERIES_REPORT_SIZE] = {0};
|
|
int ret;
|
|
unsigned int active_resolution;
|
|
|
|
if (device_version == 2)
|
|
buf[0] = STEELSERIES_ID_SETTTINGS;
|
|
else if (device_version == 3)
|
|
buf[0] = STEELSERIES_ID_SETTTINGS_PROTOCOL3;
|
|
else
|
|
return -ENOTSUP;
|
|
|
|
msleep(10);
|
|
ret = ratbag_hidraw_output_report(device, buf, buf_len);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = ratbag_hidraw_read_input_report_index(device, buf, buf_len, STEELSERIES_INPUT_HIDRAW);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (device_version == 2) {
|
|
active_resolution = buf[1] - 1;
|
|
ratbag_device_for_each_profile(device, profile) {
|
|
ratbag_profile_for_each_resolution(profile, resolution) {
|
|
resolution->is_active = resolution->index == active_resolution;
|
|
|
|
resolution->dpi_x = 100 * (1 + buf[2 + resolution->index*2]);
|
|
resolution->dpi_y = resolution->dpi_x;
|
|
}
|
|
|
|
ratbag_profile_for_each_led(profile, led) {
|
|
led->color.red = buf[6 + led->index * 3];
|
|
led->color.green = buf[7 + led->index * 3];
|
|
led->color.blue = buf[8 + led->index * 3];
|
|
}
|
|
}
|
|
} else if (device_version == 3) {
|
|
active_resolution = buf[0] - 1;
|
|
ratbag_device_for_each_profile(device, profile) {
|
|
ratbag_profile_for_each_resolution(profile, resolution) {
|
|
resolution->is_active = resolution->index == active_resolution;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
steelseries_probe(struct ratbag_device *device)
|
|
{
|
|
struct steelseries_data *drv_data = NULL;
|
|
struct ratbag_profile *profile = NULL;
|
|
struct ratbag_resolution *resolution;
|
|
struct ratbag_button *button;
|
|
struct ratbag_led *led;
|
|
int rc, button_count, led_count, device_version, mono_led, short_button;
|
|
struct dpi_list *dpilist = NULL;
|
|
struct dpi_range *dpirange = NULL;
|
|
|
|
unsigned int report_rates[] = { 125, 250, 500, 1000 };
|
|
|
|
rc = ratbag_find_hidraw(device, steelseries_test_hidraw);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = ratbag_open_hidraw_index(device,
|
|
STEELSERIES_INPUT_ENDPOINT,
|
|
STEELSERIES_INPUT_HIDRAW);
|
|
if (rc)
|
|
return rc;
|
|
|
|
device_version = ratbag_device_data_steelseries_get_device_version(device->data);
|
|
button_count = ratbag_device_data_steelseries_get_button_count(device->data);
|
|
led_count = ratbag_device_data_steelseries_get_led_count(device->data);
|
|
dpirange = ratbag_device_data_steelseries_get_dpi_range(device->data);
|
|
dpilist = ratbag_device_data_steelseries_get_dpi_list(device->data);
|
|
mono_led = ratbag_device_data_steelseries_get_mono_led(device->data);
|
|
short_button = ratbag_device_data_steelseries_get_short_button(device->data);
|
|
|
|
ratbag_device_init_profiles(device,
|
|
STEELSERIES_NUM_PROFILES,
|
|
STEELSERIES_NUM_DPI,
|
|
button_count,
|
|
led_count);
|
|
|
|
|
|
/* The device does not support reading the current settings. Fall back
|
|
to some sensible defaults */
|
|
ratbag_device_for_each_profile(device, profile) {
|
|
profile->is_active = true;
|
|
|
|
ratbag_profile_set_cap(profile, RATBAG_PROFILE_CAP_WRITE_ONLY);
|
|
ratbag_profile_set_report_rate_list(profile, report_rates,
|
|
ARRAY_LENGTH(report_rates));
|
|
ratbag_profile_set_report_rate(profile, 1000);
|
|
|
|
ratbag_profile_for_each_resolution(profile, resolution) {
|
|
if (resolution->index == 0) {
|
|
resolution->is_active = true;
|
|
resolution->is_default = true;
|
|
}
|
|
|
|
if (dpirange)
|
|
ratbag_resolution_set_dpi_list_from_range(resolution,
|
|
dpirange->min,
|
|
dpirange->max);
|
|
if (dpilist)
|
|
ratbag_resolution_set_dpi_list(resolution,
|
|
(unsigned int *)dpilist->entries,
|
|
dpilist->nentries);
|
|
|
|
/* 800 and 1600 seem as reasonable defaults supported
|
|
* by all known devices. */
|
|
resolution->dpi_x = 800 * (resolution->index + 1);
|
|
resolution->dpi_y = 800 * (resolution->index + 1);
|
|
}
|
|
|
|
ratbag_profile_for_each_button(profile, button) {
|
|
ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_BUTTON);
|
|
ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_SPECIAL);
|
|
ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_KEY);
|
|
if (short_button == 0) {
|
|
ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_MACRO);
|
|
}
|
|
|
|
button_defaults_for_layout(button, button_count);
|
|
}
|
|
|
|
ratbag_profile_for_each_led(profile, led) {
|
|
led->type = led->index == 0 ? RATBAG_LED_TYPE_LOGO : RATBAG_LED_TYPE_WHEEL;
|
|
led->mode = RATBAG_LED_ON;
|
|
if (mono_led) {
|
|
led->colordepth = RATBAG_LED_COLORDEPTH_MONOCHROME;
|
|
led->brightness = 255;
|
|
} else {
|
|
led->colordepth = RATBAG_LED_COLORDEPTH_RGB_888;
|
|
led->color.red = 0;
|
|
led->color.green = 0;
|
|
led->color.blue = 255;
|
|
}
|
|
ratbag_led_set_mode_capability(led, RATBAG_LED_OFF);
|
|
ratbag_led_set_mode_capability(led, RATBAG_LED_ON);
|
|
ratbag_led_set_mode_capability(led, RATBAG_LED_BREATHING);
|
|
if (device_version >= 2)
|
|
ratbag_led_set_mode_capability(led, RATBAG_LED_CYCLE);
|
|
}
|
|
}
|
|
|
|
drv_data = zalloc(sizeof(*drv_data));
|
|
ratbag_set_drv_data(device, drv_data);
|
|
|
|
rc = steelseries_get_firmware_version(device);
|
|
if(rc == 0)
|
|
log_debug(device->ratbag, "SteelSeries firmware version %d.%d\n",
|
|
drv_data->firmware_major,
|
|
drv_data->firmware_minor);
|
|
|
|
steelseries_read_settings(device);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
steelseries_write_dpi(struct ratbag_resolution *resolution)
|
|
{
|
|
struct ratbag_device *device = resolution->profile->device;
|
|
int device_version = ratbag_device_data_steelseries_get_device_version(device->data);
|
|
struct dpi_list *dpilist = NULL;
|
|
struct dpi_range *dpirange = NULL;
|
|
int ret;
|
|
size_t buf_len;
|
|
uint8_t buf[STEELSERIES_REPORT_SIZE] = {0};
|
|
|
|
dpirange = ratbag_device_data_steelseries_get_dpi_range(device->data);
|
|
dpilist = ratbag_device_data_steelseries_get_dpi_list(device->data);
|
|
|
|
if (device_version == 1) {
|
|
int i = 0;
|
|
|
|
/* when using lists the entries are enumerated in reverse */
|
|
if (dpilist) {
|
|
for (i = 0; i < (int)resolution->ndpis; i++) {
|
|
if (resolution->dpis[i] == resolution->dpi_x)
|
|
break;
|
|
}
|
|
i = resolution->ndpis - i;
|
|
} else {
|
|
i = resolution->dpi_x / dpirange->step - 1;
|
|
}
|
|
|
|
buf_len = STEELSERIES_REPORT_SIZE_SHORT;
|
|
buf[0] = STEELSERIES_ID_DPI_SHORT;
|
|
buf[1] = resolution->index + 1;
|
|
buf[2] = i;
|
|
} else if (device_version == 2) {
|
|
buf_len = STEELSERIES_REPORT_SIZE;
|
|
buf[0] = STEELSERIES_ID_DPI;
|
|
buf[2] = resolution->index + 1;
|
|
buf[3] = resolution->dpi_x / dpirange->step - 1;
|
|
buf[6] = 0x42; /* not sure if needed */
|
|
} else if (device_version == 3) {
|
|
buf_len = STEELSERIES_REPORT_SIZE;
|
|
buf[0] = STEELSERIES_ID_DPI_PROTOCOL3;
|
|
buf[2] = resolution->index + 1;
|
|
buf[3] = resolution->dpi_x / dpirange->step - 1;
|
|
buf[5] = 0x42; /* not sure if needed */
|
|
} else {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
msleep(10);
|
|
ret = ratbag_hidraw_output_report(device, buf, buf_len);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
steelseries_write_report_rate(struct ratbag_profile *profile)
|
|
{
|
|
struct ratbag_device *device = profile->device;
|
|
int device_version = ratbag_device_data_steelseries_get_device_version(device->data);
|
|
int ret;
|
|
size_t buf_len;
|
|
uint8_t buf[STEELSERIES_REPORT_SIZE] = {0};
|
|
|
|
if (device_version == 1) {
|
|
buf_len = STEELSERIES_REPORT_SIZE_SHORT;
|
|
buf[0] = STEELSERIES_ID_REPORT_RATE_SHORT;
|
|
buf[2] = 1000 / profile->hz;
|
|
} else if (device_version == 2) {
|
|
buf_len = STEELSERIES_REPORT_SIZE;
|
|
buf[0] = STEELSERIES_ID_REPORT_RATE;
|
|
buf[2] = 1000 / profile->hz;
|
|
} else if (device_version == 3) {
|
|
buf_len = STEELSERIES_REPORT_SIZE;
|
|
buf[0] = STEELSERIES_ID_REPORT_RATE_PROTOCOL3;
|
|
buf[2] = 1000 / profile->hz;
|
|
} else {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
msleep(10);
|
|
ret = ratbag_hidraw_output_report(device, buf, buf_len);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
steelseries_write_buttons(struct ratbag_profile *profile)
|
|
{
|
|
struct ratbag_device *device = profile->device;
|
|
struct ratbag_button *button;
|
|
int device_version = ratbag_device_data_steelseries_get_device_version(device->data);
|
|
int ret;
|
|
|
|
if (ratbag_device_data_steelseries_get_macro_length(device->data) == 0)
|
|
return 0;
|
|
|
|
int short_button = ratbag_device_data_steelseries_get_short_button(device->data);
|
|
int button_size = (short_button == 0) ? 5 : 3;
|
|
int report_size = (short_button == 0) ? STEELSERIES_REPORT_LONG_SIZE : STEELSERIES_REPORT_SIZE_SHORT;
|
|
int max_modifiers = (short_button == 0) ? 3 : 0;
|
|
|
|
uint8_t buf[report_size];
|
|
memset(buf, 0, report_size);
|
|
|
|
buf[0] = STEELSERIES_ID_BUTTONS;
|
|
|
|
ratbag_profile_for_each_button(profile, button) {
|
|
struct ratbag_button_action *action = &button->action;
|
|
uint16_t code;
|
|
unsigned int key, modifiers;
|
|
int idx;
|
|
|
|
/* Each button takes up 3 or 5 bytes starting from index 2 */
|
|
idx = 2 + button->index * button_size;
|
|
|
|
switch (action->type) {
|
|
case RATBAG_BUTTON_ACTION_TYPE_BUTTON:
|
|
buf[idx] = action->action.button;
|
|
break;
|
|
|
|
case RATBAG_BUTTON_ACTION_TYPE_MACRO:
|
|
ratbag_action_keycode_from_macro(action, &key, &modifiers);
|
|
|
|
/* There is only space for 3 modifiers */
|
|
if (__builtin_popcount(modifiers) > max_modifiers) {
|
|
log_error(device->ratbag,
|
|
"Too many modifiers in macro for button %d (maximum %d)\n",
|
|
button->index, max_modifiers);
|
|
break;
|
|
}
|
|
|
|
code = ratbag_hidraw_get_keyboard_usage_from_keycode(
|
|
device, key);
|
|
if (code) {
|
|
if (short_button) {
|
|
buf[idx] = STEELSERIES_BUTTON_KEY;
|
|
} else {
|
|
buf[idx] = STEELSERIES_BUTTON_KBD;
|
|
|
|
if (modifiers & MODIFIER_LEFTCTRL)
|
|
buf[++idx] = 0xE0;
|
|
if (modifiers & MODIFIER_LEFTSHIFT)
|
|
buf[++idx] = 0xE1;
|
|
if (modifiers & MODIFIER_LEFTALT)
|
|
buf[++idx] = 0xE2;
|
|
if (modifiers & MODIFIER_LEFTMETA)
|
|
buf[++idx] = 0xE3;
|
|
if (modifiers & MODIFIER_RIGHTCTRL)
|
|
buf[++idx] = 0xE4;
|
|
if (modifiers & MODIFIER_RIGHTSHIFT)
|
|
buf[++idx] = 0xE5;
|
|
if (modifiers & MODIFIER_RIGHTALT)
|
|
buf[++idx] = 0xE6;
|
|
if (modifiers & MODIFIER_RIGHTMETA)
|
|
buf[++idx] = 0xE7;
|
|
}
|
|
|
|
buf[idx + 1] = code;
|
|
} else {
|
|
code = ratbag_hidraw_get_consumer_usage_from_keycode(
|
|
device, action->action.key.key);
|
|
buf[idx] = STEELSERIES_BUTTON_CONSUMER;
|
|
buf[idx + 1] = code;
|
|
}
|
|
break;
|
|
|
|
case RATBAG_BUTTON_ACTION_TYPE_SPECIAL:
|
|
switch (action->action.special) {
|
|
case RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP:
|
|
buf[idx] = STEELSERIES_BUTTON_RES_CYCLE;
|
|
break;
|
|
case RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_UP:
|
|
buf[idx] = STEELSERIES_BUTTON_WHEEL_UP;
|
|
break;
|
|
case RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_DOWN:
|
|
buf[idx] = STEELSERIES_BUTTON_WHEEL_DOWN;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case RATBAG_BUTTON_ACTION_TYPE_KEY:
|
|
case RATBAG_BUTTON_ACTION_TYPE_NONE:
|
|
default:
|
|
buf[idx] = STEELSERIES_BUTTON_OFF;
|
|
break;
|
|
}
|
|
}
|
|
|
|
msleep(10);
|
|
if (device_version == 3)
|
|
ret = ratbag_hidraw_raw_request(device, STEELSERIES_ID_BUTTONS,
|
|
buf,sizeof(buf),HID_FEATURE_REPORT,HID_REQ_SET_REPORT);
|
|
else
|
|
ret = ratbag_hidraw_output_report(device, buf, sizeof(buf));
|
|
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
steelseries_write_led_v1(struct ratbag_led *led)
|
|
{
|
|
struct ratbag_device *device = led->profile->device;
|
|
uint8_t buf[STEELSERIES_REPORT_SIZE_SHORT] = {0};
|
|
int ret;
|
|
|
|
buf[0] = STEELSERIES_ID_LED_EFFECT_SHORT;
|
|
buf[1] = led->index + 1;
|
|
|
|
switch(led->mode) {
|
|
case RATBAG_LED_OFF:
|
|
case RATBAG_LED_ON:
|
|
buf[2] = 0x01;
|
|
break;
|
|
case RATBAG_LED_BREATHING:
|
|
buf[2] = 0x03; /* 0x02: slow, 0x03: medium, 0x04: fast */
|
|
break;
|
|
case RATBAG_LED_CYCLE:
|
|
/* Cycle mode is not supported on this version */
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
msleep(10);
|
|
ret = ratbag_hidraw_output_report(device, buf, sizeof(buf));
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
int mono_led = ratbag_device_data_steelseries_get_mono_led(device->data);
|
|
|
|
if (mono_led) {
|
|
buf[0] = STEELSERIES_ID_LED_INTENSITY_SHORT;
|
|
buf[1] = led->index + 1;
|
|
if (led->mode == RATBAG_LED_OFF || led->brightness == 0) {
|
|
buf[2] = 1;
|
|
} else {
|
|
//split the brightness into roughly 3 equal intensities
|
|
buf[2] = (led->brightness / 86) + 2;
|
|
}
|
|
} else {
|
|
buf[0] = STEELSERIES_ID_LED_COLOR_SHORT;
|
|
buf[1] = led->index + 1;
|
|
buf[2] = led->color.red;
|
|
buf[3] = led->color.green;
|
|
buf[4] = led->color.blue;
|
|
}
|
|
|
|
msleep(10);
|
|
ret = ratbag_hidraw_output_report(device, buf, sizeof(buf));
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
create_cycle(struct steelseries_led_cycle *cycle)
|
|
{
|
|
cycle->led_id = 0x00;
|
|
cycle->duration = 5000;
|
|
cycle->repeat = true;
|
|
cycle->trigger_buttons = 0x00;
|
|
|
|
list_init(&cycle->points);
|
|
}
|
|
|
|
static void
|
|
construct_cycle_buffer(struct steelseries_led_cycle *cycle,
|
|
struct steelseries_led_cycle_spec *spec,
|
|
uint8_t *buf,
|
|
uint8_t buf_size)
|
|
{
|
|
struct steelseries_point *point;
|
|
uint16_t duration;
|
|
uint8_t npoints = 0;
|
|
uint16_t cycle_size = 0;
|
|
|
|
buf[0] = spec->cmd_val;
|
|
buf[spec->led_id_idx] = cycle->led_id;
|
|
if (spec->has_2_led_ids)
|
|
buf[spec->led_id2_idx] = cycle->led_id;
|
|
|
|
if (!cycle->repeat)
|
|
buf[spec->repeat_idx] = 0x01;
|
|
|
|
buf[spec->trigger_idx] = cycle->trigger_buttons;
|
|
|
|
int color_idx = spec->header_len;
|
|
|
|
list_for_each(point, &cycle->points, link) {
|
|
if(npoints == 0){
|
|
/* this is not a mistake,
|
|
we need to write the first
|
|
point as the first data after the header */
|
|
buf[color_idx++] = point->color.red;
|
|
buf[color_idx++] = point->color.green;
|
|
buf[color_idx++] = point->color.blue;
|
|
}
|
|
|
|
cycle_size += point->pos;
|
|
assert(cycle_size < 256);
|
|
assert(34 + npoints*4 <= buf_size);
|
|
|
|
buf[(color_idx ) + npoints*4] = point->color.red;
|
|
buf[(color_idx+1) + npoints*4] = point->color.green;
|
|
buf[(color_idx+2) + npoints*4] = point->color.blue;
|
|
buf[(color_idx+3) + npoints*4] = point->pos;
|
|
|
|
npoints++;
|
|
}
|
|
|
|
buf[spec->point_count_idx] = npoints;
|
|
|
|
/* this seems to be the minimum allowed */
|
|
duration = max(buf[spec->point_count_idx] * 330, cycle->duration);
|
|
|
|
hidpp_set_unaligned_le_u16(&buf[spec->duration_idx], duration);
|
|
}
|
|
|
|
static int
|
|
steelseries_write_led_cycle(struct ratbag_led *led,
|
|
struct steelseries_led_cycle_spec *cycle_spec)
|
|
{
|
|
struct ratbag_device *device = led->profile->device;
|
|
uint8_t buf[STEELSERIES_REPORT_SIZE] = {0};
|
|
int device_version = ratbag_device_data_steelseries_get_device_version(device->data);
|
|
int ret;
|
|
|
|
struct steelseries_led_cycle cycle;
|
|
struct steelseries_point point[4];
|
|
|
|
const struct ratbag_color black = { 0x00 };
|
|
const struct ratbag_color red = { 0xFF, 0x00, 0x00 };
|
|
const struct ratbag_color green = { 0x00, 0xFF, 0x00 };
|
|
const struct ratbag_color blue = { 0x00, 0x00, 0xFF };
|
|
|
|
create_cycle(&cycle);
|
|
cycle.led_id = led->index;
|
|
|
|
switch(led->mode) {
|
|
case RATBAG_LED_OFF:
|
|
cycle.repeat = false;
|
|
|
|
point[0].color = black;
|
|
point[0].pos = 0x00;
|
|
|
|
list_append(&cycle.points, &point[0].link);
|
|
break;
|
|
case RATBAG_LED_ON:
|
|
cycle.repeat = false;
|
|
|
|
point[0].color = led->color;
|
|
point[0].pos = 0x00;
|
|
|
|
list_append(&cycle.points, &point[0].link);
|
|
break;
|
|
case RATBAG_LED_CYCLE:
|
|
point[0].color = red;
|
|
point[0].pos = 0x00;
|
|
|
|
point[1].color = green;
|
|
point[1].pos = 0x55;
|
|
|
|
point[2].color = blue;
|
|
point[2].pos = 0x55;
|
|
|
|
point[3].color = red;
|
|
point[3].pos = 0x55;
|
|
|
|
list_append(&cycle.points, &point[0].link);
|
|
list_append(&cycle.points, &point[1].link);
|
|
list_append(&cycle.points, &point[2].link);
|
|
list_append(&cycle.points, &point[3].link);
|
|
|
|
cycle.duration = led->ms;
|
|
break;
|
|
case RATBAG_LED_BREATHING:
|
|
point[0].color = black;
|
|
point[0].pos = 0x00;
|
|
|
|
point[1].color = led->color;
|
|
point[1].pos = 0x7F;
|
|
|
|
point[2].color = black;
|
|
point[2].pos = 0x7F;
|
|
|
|
list_append(&cycle.points, &point[0].link);
|
|
list_append(&cycle.points, &point[1].link);
|
|
list_append(&cycle.points, &point[2].link);
|
|
|
|
cycle.duration = led->ms;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
construct_cycle_buffer(&cycle, cycle_spec, buf, sizeof(buf));
|
|
|
|
msleep(10);
|
|
if (device_version == 3)
|
|
ret = ratbag_hidraw_raw_request(device, cycle_spec->cmd_val, buf,
|
|
sizeof(buf), cycle_spec->hid_report_type, HID_REQ_SET_REPORT);
|
|
else
|
|
ret = ratbag_hidraw_output_report(device, buf, sizeof(buf));
|
|
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
steelseries_write_led_v2(struct ratbag_led *led)
|
|
{
|
|
struct steelseries_led_cycle_spec spec;
|
|
|
|
spec.hid_report_type = HID_OUTPUT_REPORT;
|
|
spec.header_len = 28;
|
|
spec.cmd_val = STEELSERIES_ID_LED;
|
|
spec.has_2_led_ids = false;
|
|
spec.led_id_idx = 2;
|
|
spec.duration_idx = 3;
|
|
spec.repeat_idx = 19;
|
|
spec.trigger_idx = 23;
|
|
spec.point_count_idx = 27;
|
|
|
|
return steelseries_write_led_cycle(led, &spec);
|
|
}
|
|
|
|
static int
|
|
steelseries_write_led_v3(struct ratbag_led *led)
|
|
{
|
|
struct steelseries_led_cycle_spec spec;
|
|
|
|
spec.hid_report_type = HID_FEATURE_REPORT;
|
|
spec.header_len = 30;
|
|
spec.cmd_val = STEELSERIES_ID_LED_PROTOCOL3;
|
|
spec.has_2_led_ids = true;
|
|
spec.led_id_idx = 2;
|
|
spec.led_id2_idx = 7;
|
|
spec.duration_idx = 8;
|
|
spec.repeat_idx = 24;
|
|
spec.trigger_idx = 25;
|
|
spec.point_count_idx = 29;
|
|
|
|
return steelseries_write_led_cycle(led, &spec);
|
|
}
|
|
|
|
static int
|
|
steelseries_write_led(struct ratbag_led *led)
|
|
{
|
|
struct ratbag_device *device = led->profile->device;
|
|
int device_version = ratbag_device_data_steelseries_get_device_version(device->data);
|
|
|
|
if (device_version == 1)
|
|
return steelseries_write_led_v1(led);
|
|
else if (device_version == 2)
|
|
return steelseries_write_led_v2(led);
|
|
else if (device_version == 3)
|
|
return steelseries_write_led_v3(led);
|
|
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
static int
|
|
steelseries_write_save(struct ratbag_device *device)
|
|
{
|
|
int device_version = ratbag_device_data_steelseries_get_device_version(device->data);
|
|
int ret;
|
|
size_t buf_len;
|
|
uint8_t buf[STEELSERIES_REPORT_SIZE] = {0};
|
|
|
|
if (device_version == 1) {
|
|
buf_len = STEELSERIES_REPORT_SIZE_SHORT;
|
|
buf[0] = STEELSERIES_ID_SAVE_SHORT;
|
|
} else if (device_version == 2) {
|
|
buf_len = STEELSERIES_REPORT_SIZE;
|
|
buf[0] = STEELSERIES_ID_SAVE;
|
|
} else if (device_version == 3) {
|
|
buf_len = STEELSERIES_REPORT_SIZE;
|
|
buf[0] = STEELSERIES_ID_SAVE_PROTOCOL3;
|
|
} else {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
msleep(20);
|
|
ret = ratbag_hidraw_output_report(device, buf, buf_len);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
steelseries_write_profile(struct ratbag_profile *profile)
|
|
{
|
|
struct ratbag_resolution *resolution;
|
|
struct ratbag_button *button;
|
|
struct ratbag_led *led;
|
|
int rc;
|
|
bool buttons_dirty = false;
|
|
|
|
rc = steelseries_write_report_rate(profile);
|
|
if (rc != 0)
|
|
return rc;
|
|
|
|
ratbag_profile_for_each_resolution(profile, resolution) {
|
|
if (!resolution->dirty)
|
|
continue;
|
|
|
|
rc = steelseries_write_dpi(resolution);
|
|
if (rc != 0)
|
|
return rc;
|
|
|
|
/* The same hz is used for all resolutions. Only write once. */
|
|
if (resolution->index > 0)
|
|
continue;
|
|
|
|
}
|
|
|
|
ratbag_profile_for_each_button(profile, button) {
|
|
if (button->dirty)
|
|
buttons_dirty = true;
|
|
}
|
|
|
|
if (buttons_dirty) {
|
|
rc = steelseries_write_buttons(profile);
|
|
if (rc != 0)
|
|
return rc;
|
|
}
|
|
|
|
ratbag_profile_for_each_led(profile, led) {
|
|
if (!led->dirty)
|
|
continue;
|
|
|
|
rc = steelseries_write_led(led);
|
|
if (rc != 0)
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
steelseries_commit(struct ratbag_device *device)
|
|
{
|
|
struct ratbag_profile *profile;
|
|
int rc = 0;
|
|
|
|
list_for_each(profile, &device->profiles, link) {
|
|
if (!profile->dirty)
|
|
continue;
|
|
|
|
log_debug(device->ratbag,
|
|
"Profile %d changed, rewriting\n", profile->index);
|
|
|
|
rc = steelseries_write_profile(profile);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* persist the current settings on the device */
|
|
|
|
rc = steelseries_write_save(device);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
steelseries_remove(struct ratbag_device *device)
|
|
{
|
|
ratbag_close_hidraw_index(device, 0);
|
|
ratbag_close_hidraw_index(device, 1);
|
|
free(ratbag_get_drv_data(device));
|
|
}
|
|
|
|
struct ratbag_driver steelseries_driver = {
|
|
.name = "SteelSeries",
|
|
.id = "steelseries",
|
|
.probe = steelseries_probe,
|
|
.remove = steelseries_remove,
|
|
.commit = steelseries_commit,
|
|
};
|