diff --git a/hwdb/70-libratbag-mouse.hwdb b/hwdb/70-libratbag-mouse.hwdb index d32965bb..8097d319 100644 --- a/hwdb/70-libratbag-mouse.hwdb +++ b/hwdb/70-libratbag-mouse.hwdb @@ -187,3 +187,12 @@ mouse:usb:v046dpc539:name:*: mouse:usb:v1e7dp2e22:name:*: RATBAG_DRIVER=roccat RATBAG_SVG=roccat-kone-xtd.svg + + +########################################## +# G.Skill +########################################## + +# MX-780 +mouse:usb:v28dap3101:name:*: + RATBAG_DRIVER=gskill diff --git a/src/Makefile.am b/src/Makefile.am index 8ad3b162..0d9219f8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -12,6 +12,7 @@ libratbag_la_SOURCES = \ driver-hidpp20.c \ driver-hidpp10.c \ driver-roccat.c \ + driver-gskill.c \ driver-test.c \ libratbag.c \ libratbag.h \ diff --git a/src/driver-gskill.c b/src/driver-gskill.c new file mode 100644 index 00000000..610f7041 --- /dev/null +++ b/src/driver-gskill.c @@ -0,0 +1,1469 @@ +/* + * Copyright © 2016 Red Hat, Inc. + * + * 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 "libratbag-private.h" +#include "libratbag-hidraw.h" + +#include +#include + +#define GSKILL_PROFILE_MAX 5 +#define GSKILL_NUM_DPI 5 +#define GSKILL_BUTTON_MAX 10 + +#define GSKILL_MAX_POLLING_RATE 1000 + +#define GSKILL_MIN_DPI 100 +#define GSKILL_MAX_DPI 8200 +#define GSKILL_DPI_UNIT 50 + +/* Commands */ +#define GSKILL_GET_CURRENT_PROFILE_NUM 0x03 +#define GSKILL_GET_SET_MACRO 0x04 +#define GSKILL_GET_SET_PROFILE 0x05 +#define GSKILL_GENERAL_CMD 0x0c + +#define GSKILL_REPORT_SIZE_PROFILE 644 +#define GSKILL_REPORT_SIZE_CMD 9 +#define GSKILL_REPORT_SIZE_MACRO 2052 + +#define GSKILL_CHECKSUM_OFFSET 3 + +/* Command status codes */ +#define GSKILL_CMD_SUCCESS 0xb0 +#define GSKILL_CMD_IN_PROGRESS 0xb1 +#define GSKILL_CMD_FAILURE 0xb2 +#define GSKILL_CMD_IDLE 0xb3 + +/* LED groups. DPI is omitted here since it's handled specially */ +#define GSKILL_LED_TYPE_LOGO 0 +#define GSKILL_LED_TYPE_WHEEL 1 +#define GSKILL_LED_TYPE_TAIL 2 +#define GSKILL_LED_TYPE_COUNT 3 + +#define GSKILL_KBD_MOD_CTRL_LEFT AS_MASK(0) +#define GSKILL_KBD_MOD_SHIFT_LEFT AS_MASK(1) +#define GSKILL_KBD_MOD_ALT_LEFT AS_MASK(2) +#define GSKILL_KBD_MOD_SUPER_LEFT AS_MASK(3) +#define GSKILL_KBD_MOD_CTRL_RIGHT AS_MASK(4) +#define GSKILL_KBD_MOD_SHIFT_RIGHT AS_MASK(5) +#define GSKILL_KBD_MOD_ALT_RIGHT AS_MASK(6) +#define GSKILL_KBD_MOD_SUPER_RIGHT AS_MASK(7) + +struct gskill_raw_dpi_level { + uint8_t x; + uint8_t y; +} __attribute__((packed)); + +struct gskill_led_color { + uint8_t red; + uint8_t green; + uint8_t blue; +} __attribute__((packed)); + +struct gskill_led_values { + uint8_t brightness; + struct gskill_led_color color; +} __attribute__((packed)); + +enum gskill_led_control_type { + GSKILL_LED_ALL_OFF = 0x0, + GSKILL_LED_ALL_ON = 0x1, + GSKILL_LED_BREATHING = 0x2, + GSKILL_DPI_LED_RIGHT_CYCLE = 0x3, + GSKILL_DPI_LED_LEFT_CYCLE = 0x4 +}; + +struct gskill_background_led_cfg { + uint8_t brightness; + struct gskill_led_color dpi[4]; + struct gskill_led_color leds[GSKILL_LED_TYPE_COUNT]; +} __attribute__((packed)); + +struct gskill_dpi_led_group_cfg { + uint8_t duration_step; + uint8_t duration_high; + uint8_t duration_low; + uint8_t cycle_num; + + struct gskill_led_values steps[12]; +}; + +struct gskill_led_group_cfg { + enum gskill_led_control_type type :3; + uint8_t :5; /* unused */ + + uint8_t duration_step; + uint8_t duration_high; + uint8_t duration_low; + uint8_t cycle_num; + + struct gskill_led_values steps[12]; +} __attribute__((packed)); + +struct gskill_dpi_led_cycle_cfg { + enum gskill_led_control_type type :3; + uint8_t :5; /* unused */ + + /* Don't worry, the low/high flip-flop here is intentional */ + uint8_t duration_low; + uint8_t duration_high; + uint8_t cycle_num; + + struct gskill_led_values cycles[12]; +} __attribute__((packed)); + +/* + * We may occasionally run into codes outside this, however those codes + * indicate functionalities that aren't too useful for us + */ +enum gskill_button_function_type { + GSKILL_BUTTON_FUNCTION_WHEEL = 0x00, + GSKILL_BUTTON_FUNCTION_MOUSE = 0x01, + GSKILL_BUTTON_FUNCTION_KBD = 0x02, + GSKILL_BUTTON_FUNCTION_CONSUMER = 0x03, + GSKILL_BUTTON_FUNCTION_MACRO = 0x06, + GSKILL_BUTTON_FUNCTION_DPI_UP = 0x09, + GSKILL_BUTTON_FUNCTION_DPI_DOWN = 0x0a, + GSKILL_BUTTON_FUNCTION_CYCLE_DPI_UP = 0x0b, + GSKILL_BUTTON_FUNCTION_CYCLE_DPI_DOWN = 0x0c, + GSKILL_BUTTON_FUNCTION_PROFILE_SWITCH = 0x0d, + GSKILL_BUTTON_FUNCTION_TEMPORARY_CPI_ADJUST = 0x15, + GSKILL_BUTTON_FUNCTION_DIRECT_DPI_CHANGE = 0x16, + GSKILL_BUTTON_FUNCTION_CYCLE_PROFILE_UP = 0x18, + GSKILL_BUTTON_FUNCTION_CYCLE_PROFILE_DOWN = 0x19, + GSKILL_BUTTON_FUNCTION_DISABLE = 0xff +}; + +struct gskill_button_cfg { + enum gskill_button_function_type type :8; + union { + struct { + enum { + GSKILL_WHEEL_SCROLL_UP = 0, + GSKILL_WHEEL_SCROLL_DOWN = 1, + } direction :8; + } wheel; + + struct { + enum { + GSKILL_BTN_MASK_LEFT = AS_MASK(0), + GSKILL_BTN_MASK_RIGHT = AS_MASK(1), + GSKILL_BTN_MASK_MIDDLE = AS_MASK(2), + GSKILL_BTN_MASK_SIDE = AS_MASK(3), + GSKILL_BTN_MASK_EXTRA = AS_MASK(4) + } button_mask :8; + } mouse; + + struct { + uint16_t code; + } consumer; + + struct { + uint8_t modifier_mask; + uint8_t hid_code; + /* + * XXX: Supposedly this is supposed to have additional + * parts of the kbd code, however that doesn't seem to + * be the case in practice… + */ + uint16_t :16; + } kbd; + + struct { + uint8_t level; + } dpi; + } params; +} __attribute__((packed)); + +struct gskill_action_mapping { + struct gskill_button_cfg config; + struct ratbag_button_action action; +}; + +struct gskill_profile_report { + uint16_t :16; + uint8_t profile_num; + uint8_t checksum; + uint8_t polling_rate :4; + uint8_t angle_snap_ratio :4; + uint8_t liftoff_value :5; + bool liftoff_enabled :1; + uint16_t :10; /* unused */ + + uint8_t current_dpi_level :4; + uint8_t dpi_num :4; + struct gskill_raw_dpi_level dpi_levels[GSKILL_NUM_DPI]; + + /* LEDs */ + struct gskill_background_led_cfg background_lighting; + struct gskill_dpi_led_cycle_cfg led_dpi_cycle; + struct gskill_dpi_led_group_cfg dpi_led; + struct gskill_led_group_cfg leds[GSKILL_LED_TYPE_COUNT]; + + /* Button assignments */ + uint8_t button_function_redirections[8]; + struct gskill_button_cfg btn_cfgs[GSKILL_BUTTON_MAX]; + + /* A mystery */ + uint8_t _unused1[27]; + + char name[256]; +} __attribute__((packed)); + +_Static_assert(sizeof(struct gskill_profile_report) == GSKILL_REPORT_SIZE_PROFILE, + "Size of gskill_profile_report is wrong"); + +struct gskill_macro_delay { + uint8_t tag; /* should be 0x1 to indicate delay */ + uint16_t count; +} __attribute__((packed)); + +struct gskill_macro_report { + /* yes, the report can be both at offset 0 and 1 :( */ + union { + struct { + uint8_t result; + uint8_t report_id; + } read; + struct { + uint8_t report_id; + uint8_t :8; /* unused */ + } write; + } header; + + uint8_t macro_num; + uint8_t checksum; + + enum { + GSKILL_MACRO_METHOD_BUTTON_PRESS = 0x5, + GSKILL_MACRO_METHOD_BUTTON_RELEASE = 0x1, + GSKILL_MACRO_METHOD_BUTTON_LOOP_START = 0x7, + GSKILL_MACRO_METHOD_BUTTON_LOOP_END = 0x0 + } macro_exec_method :8; + + uint8_t loop_count; + uint8_t please_set_me_to_1_thank_you; + uint16_t macro_length; + uint8_t macro_name_length; + char macro_name[256]; + uint8_t macro_content[1786]; +} __attribute__((packed)); + +_Static_assert(sizeof(struct gskill_macro_report) == GSKILL_REPORT_SIZE_MACRO, + "Size of gskill_macro_report is wrong"); + +enum ratbag_button_type gskill_button_type_mapping[] = { + RATBAG_BUTTON_TYPE_LEFT, + RATBAG_BUTTON_TYPE_RIGHT, + RATBAG_BUTTON_TYPE_MIDDLE, + RATBAG_BUTTON_TYPE_THUMB, + RATBAG_BUTTON_TYPE_THUMB2, + RATBAG_BUTTON_TYPE_RESOLUTION_CYCLE_UP, + RATBAG_BUTTON_TYPE_THUMB3, + RATBAG_BUTTON_TYPE_THUMB4, + RATBAG_BUTTON_TYPE_WHEEL_UP, + RATBAG_BUTTON_TYPE_WHEEL_DOWN, +}; + +struct gskill_button_function_mapping { + enum gskill_button_function_type type; + struct ratbag_button_action action; +}; + +static const struct gskill_button_function_mapping gskill_button_function_mapping[] = { + { GSKILL_BUTTON_FUNCTION_MACRO, BUTTON_ACTION_MACRO }, + { GSKILL_BUTTON_FUNCTION_DPI_UP, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP) }, + { GSKILL_BUTTON_FUNCTION_DPI_DOWN, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN) }, + { GSKILL_BUTTON_FUNCTION_CYCLE_DPI_UP, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP) }, + { GSKILL_BUTTON_FUNCTION_CYCLE_PROFILE_UP, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP) }, + { GSKILL_BUTTON_FUNCTION_CYCLE_PROFILE_DOWN, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_DOWN) }, + { GSKILL_BUTTON_FUNCTION_DISABLE, BUTTON_ACTION_NONE }, +}; + +struct gskill_profile_data { + struct gskill_profile_report report; + uint8_t res_idx_to_dev_idx[GSKILL_NUM_DPI]; + + struct gskill_macro_report macros[GSKILL_BUTTON_MAX]; +}; + +struct gskill_data { + struct gskill_profile_data profile_data[GSKILL_PROFILE_MAX]; +}; + +static const struct ratbag_button_action * +gskill_button_function_to_action(enum gskill_button_function_type type) +{ + const struct gskill_button_function_mapping *mapping; + + ARRAY_FOR_EACH(gskill_button_function_mapping, mapping) { + if (mapping->type == type) + return &mapping->action; + } + + return NULL; +} + +static enum gskill_button_function_type +gskill_button_function_from_action(const struct ratbag_button_action *action) +{ + const struct gskill_button_function_mapping *mapping; + + ARRAY_FOR_EACH(gskill_button_function_mapping, mapping) { + if (ratbag_button_action_match(&mapping->action, action)) + return mapping->type; + } + + return GSKILL_BUTTON_FUNCTION_DISABLE; +} + +static uint8_t +gskill_calculate_checksum(const uint8_t *buf, size_t len) +{ + uint8_t checksum = 0; + unsigned i; + + for (i = GSKILL_CHECKSUM_OFFSET + 1; i < len; i++) + checksum += buf[i]; + + checksum = ~checksum + 1; + + return checksum; +} + +static int +gskill_general_cmd(struct ratbag_device *device, + uint8_t buf[GSKILL_REPORT_SIZE_CMD]) { + int rc; + int retries; + + assert(buf[0] == GSKILL_GENERAL_CMD); + + rc = ratbag_hidraw_raw_request(device, GSKILL_GENERAL_CMD, buf, + GSKILL_REPORT_SIZE_CMD, + HID_FEATURE_REPORT, HID_REQ_SET_REPORT); + if (rc != GSKILL_REPORT_SIZE_CMD) { + log_error(device->ratbag, + "Error while sending command to mouse: %d\n", rc); + return rc < 0 ? rc : -EPROTO; + } + + rc = -EAGAIN; + for (retries = 0; retries < 10 && rc == -EAGAIN; retries++) { + /* + * Wait for the device to be ready + * Spec says this should be 10ms, but 20ms seems to get the + * mouse to return slightly less nonsense responses + */ + msleep(20); + + rc = ratbag_hidraw_raw_request(device, 0, buf, + GSKILL_REPORT_SIZE_CMD, + HID_FEATURE_REPORT, + HID_REQ_GET_REPORT); + /* + * Sometimes the mouse just doesn't send anything when it wants + * to tell us it's ready. In this case rc will be 0 and this + * function will succeed. + */ + if (rc < GSKILL_REPORT_SIZE_CMD) + break; + + /* Check the command status bit */ + switch (buf[1]) { + case 0: /* sometimes the mouse gets lazy and just returns a + blank buffer on success */ + case GSKILL_CMD_SUCCESS: + rc = 0; + break; + + case GSKILL_CMD_IN_PROGRESS: + rc = -EAGAIN; + continue; + + case GSKILL_CMD_IDLE: + log_error(device->ratbag, + "Command response indicates idle status? Uh huh.\n"); + rc = -EPROTO; + break; + + case GSKILL_CMD_FAILURE: + log_error(device->ratbag, "Command failed\n"); + rc = -EIO; + break; + + default: + log_error(device->ratbag, + "Received unknown command status from mouse: 0x%x\n", + buf[1]); + rc = -EPROTO; + break; + } + } + + if (rc == -EAGAIN) { + log_error(device->ratbag, + "Failed to get command response from mouse after %d tries, giving up\n", + retries); + rc = -ETIMEDOUT; + } else if (rc) { + log_error(device->ratbag, + "Failed to perform command on mouse: %d\n", + rc); + + if (rc > 0) + rc = -EPROTO; + } + + return rc; +} + +static int +gskill_get_active_profile_idx(struct ratbag_device *device) +{ + uint8_t buf[GSKILL_REPORT_SIZE_CMD] = { GSKILL_GENERAL_CMD, 0xc4, 0x7, + 0x0, 0x1 }; + int rc; + + rc = gskill_general_cmd(device, buf); + if (rc) { + log_error(device->ratbag, + "Error while getting active profile number from mouse: %d\n", + rc); + return rc; + } + + return buf[3]; +} + +static int +gskill_set_active_profile(struct ratbag_device *device, unsigned index) +{ + uint8_t buf[GSKILL_REPORT_SIZE_CMD] = { GSKILL_GENERAL_CMD, 0xc4, 0x7, + index, 0x0 }; + int rc; + + rc = gskill_general_cmd(device, buf); + if (rc) { + log_error(device->ratbag, + "Error while changing active profile on mouse: %d\n", + rc); + return rc; + } + + return 0; +} + +static int +gskill_get_profile_count(struct ratbag_device *device) +{ + uint8_t buf[GSKILL_REPORT_SIZE_CMD] = { GSKILL_GENERAL_CMD, 0xc4, 0x12, + 0x0, 0x1 }; + int rc; + + rc = gskill_general_cmd(device, buf); + if (rc) { + log_error(device->ratbag, + "Error while getting the number of profiles: %d\n", + rc); + return rc; + } + + return buf[3]; +} + +static int +gskill_set_profile_count(struct ratbag_device *device, unsigned int count) +{ + uint8_t buf[GSKILL_REPORT_SIZE_CMD] = { GSKILL_GENERAL_CMD, 0xc4, 0x12, + count, 0x0 }; + int rc; + + rc = gskill_general_cmd(device, buf); + if (rc) { + log_error(device->ratbag, + "Error while setting the number of profiles: %d\n", + rc); + return rc; + } + + return 0; +} + +/* + * This is used for setting the profile index argument on the mouse for both + * reading and writing profiles + */ +static int +gskill_select_profile(struct ratbag_device *device, unsigned index, bool write) +{ + uint8_t buf[GSKILL_REPORT_SIZE_CMD] = { GSKILL_GENERAL_CMD, 0xc4, 0x0c, + index, write }; + int rc; + + /* + * While this looks like a normal command and should have the same + * behavior, trying to receive the command return status from the mouse + * breaks reading the profile + */ + rc = ratbag_hidraw_raw_request(device, GSKILL_GENERAL_CMD, + buf, sizeof(buf), HID_FEATURE_REPORT, + HID_REQ_SET_REPORT); + if (rc != sizeof(buf)) { + log_error(device->ratbag, + "Error while setting profile number to read/write: %d\n", + rc); + return rc < 0 ? rc : -EPROTO; + } + + return 0; +} + +/* + * Instructs the mouse to reload the data from a profile we've just written to + * it. + */ +static int +gskill_reload_profile_data(struct ratbag_device *device) +{ + uint8_t buf[GSKILL_REPORT_SIZE_CMD] = { GSKILL_GENERAL_CMD, 0xc4, 0x0 }; + int rc; + + log_debug(device->ratbag, "Asking mouse to reload profile data\n"); + + rc = gskill_general_cmd(device, buf); + if (rc) { + log_error(device->ratbag, + "Failed to get mouse to reload profile data: %d\n", + rc); + return rc; + } + + return 0; +} + +static int +gskill_do_write_profile(struct ratbag_device *device, + struct gskill_profile_report *report) +{ + uint8_t *buf = (uint8_t*)report; + int rc; + + report->checksum = gskill_calculate_checksum(buf, sizeof(*report)); + + /* + * The G.Skill configuration software doesn't take kindly to blank + * profile names, so ensure we have one + */ + if (report->name[0] == '\0') { + log_debug(device->ratbag, + "Setting profile name to \"Ratbag profile %d\"\n", + report->profile_num); + rc = ratbag_utf8_to_enc(report->name, sizeof(report->name), + "UTF16-LE", + "Ratbag profile %d", + report->profile_num); + if (rc < 0) + return rc; + } + + rc = gskill_select_profile(device, report->profile_num, true); + if (rc) + return rc; + + /* Wait for the device to be ready */ + msleep(200); + + rc = ratbag_hidraw_raw_request(device, GSKILL_GET_SET_PROFILE, + buf, sizeof(*report), HID_FEATURE_REPORT, + HID_REQ_SET_REPORT); + if (rc != sizeof(*report)) { + log_error(device->ratbag, + "Error while writing profile: %d\n", rc); + return rc < 0 ? rc : -EPROTO; + } + + rc = gskill_reload_profile_data(device); + if (rc) + return rc; + + return 0; +} + +static int +gskill_get_firmware_version(struct ratbag_device *device) { + uint8_t buf[GSKILL_REPORT_SIZE_CMD] = { GSKILL_GENERAL_CMD, 0xc4, 0x08 }; + int rc; + + rc = gskill_general_cmd(device, buf); + if (rc) { + log_error(device->ratbag, + "Failed to read the firmware version of the mouse: %d\n", + rc); + return rc; + } + + return buf[4]; +} + +static unsigned int +gskill_mouse_button_macro_code_to_keycode(uint8_t code) +{ + unsigned int keycode; + + switch (code & 0x0f) { + case 0x8: keycode = BTN_LEFT; break; + case 0x9: keycode = BTN_RIGHT; break; + case 0xa: keycode = BTN_MIDDLE; break; + case 0xb: keycode = BTN_SIDE; break; + case 0xc: keycode = BTN_EXTRA; break; + default: keycode = 0; break; + } + + return keycode; +} + +static uint8_t +gskill_macro_code_from_event(struct ratbag_device *device, + struct ratbag_macro_event *event) +{ + uint8_t macro_code; + + /* + * The miscellanious keycodes are ORd with 0x70 to indicate press, 0xF0 + * to indicate release + */ + if (event->type == RATBAG_MACRO_EVENT_KEY_PRESSED) + macro_code = 0x70; + else + macro_code = 0xF0; + + switch (event->event.key) { + case KEY_LEFTCTRL: macro_code |= 0x00; break; + case KEY_LEFTSHIFT: macro_code |= 0x01; break; + case KEY_LEFTALT: macro_code |= 0x02; break; + case KEY_LEFTMETA: macro_code |= 0x03; break; + case KEY_RIGHTCTRL: macro_code |= 0x04; break; + case KEY_RIGHTSHIFT: macro_code |= 0x05; break; + case KEY_RIGHTALT: macro_code |= 0x06; break; + case KEY_RIGHTMETA: macro_code |= 0x07; break; + case BTN_LEFT: macro_code |= 0x08; break; + case BTN_RIGHT: macro_code |= 0x09; break; + case BTN_MIDDLE: macro_code |= 0x0a; break; + case BTN_SIDE: macro_code |= 0x0b; break; + case BTN_EXTRA: macro_code |= 0x0c; break; + case KEY_SCROLLDOWN: macro_code = 0x7e; break; + case KEY_SCROLLUP: macro_code = 0xfe; break; + default: + macro_code = ratbag_hidraw_get_keyboard_usage_from_keycode( + device, event->event.key); + + if (event->type == RATBAG_MACRO_EVENT_KEY_RELEASED) + macro_code += 0x80; + break; + } + + return macro_code; +} + +static struct ratbag_button_macro * +gskill_macro_from_report(struct ratbag_device *device, + struct gskill_macro_report *report) +{ + struct ratbag_button_macro *macro; + enum ratbag_macro_event_type type; + const uint8_t *data = (uint8_t*)&report->macro_content; + unsigned int event_data; + int ret, i, event_idx, increment; + + /* The macro is empty */ + if (report->macro_length == 0xff) { + return NULL; + } else if (report->macro_length > sizeof(report->macro_content)) { + log_error(device->ratbag, + "Macro length too large (max should be %ld, we got %d)\n", + sizeof(report->macro_content), report->macro_length); + return NULL; + } + + /* + * Since the length is only 8 bits long, it's impossible to specify a + * length that's too large for the macro name + */ + macro = ratbag_button_macro_new(NULL); + ret = ratbag_utf8_from_enc(report->macro_name, + report->macro_name_length, "UTF-16LE", + ¯o->macro.name); + if (ret < 0) + return NULL; + + for (i = 0, event_idx = 0, increment = 1; + i < report->macro_length; + i += increment, increment = 1, event_idx++) { + const struct gskill_macro_delay *delay; + + switch (data[i]) { + case 0x01: /* delay */ + delay = (struct gskill_macro_delay*)&data[i]; + increment = sizeof(struct gskill_macro_delay); + + type = RATBAG_MACRO_EVENT_WAIT; + event_data = delay->count; + + break; + case 0x04 ... 0x6a: /* HID KBD code, press */ + type = RATBAG_MACRO_EVENT_KEY_PRESSED; + event_data = ratbag_hidraw_get_keycode_from_keyboard_usage( + device, data[i]); + break; + case 0x70 ... 0x77: /* KBD modifier, press */ + type = RATBAG_MACRO_EVENT_KEY_PRESSED; + event_data = ratbag_hidraw_get_keycode_from_keyboard_usage( + device, data[i] + 0x70); + break; + case 0x78 ... 0x7c: /* Mouse button, press */ + type = RATBAG_MACRO_EVENT_KEY_PRESSED; + event_data = gskill_mouse_button_macro_code_to_keycode( + data[i]); + break; + case 0x7e: /* Scroll down */ + type = RATBAG_MACRO_EVENT_KEY_PRESSED; + event_data = KEY_SCROLLDOWN; + break; + case 0x84 ... 0xef: /* HID KBD code, release */ + type = RATBAG_MACRO_EVENT_KEY_RELEASED; + event_data = ratbag_hidraw_get_keycode_from_keyboard_usage( + device, data[i] - 0x80); + break; + case 0xf0 ... 0xf7: /* KBD modifier, release */ + type = RATBAG_MACRO_EVENT_KEY_RELEASED; + event_data = ratbag_hidraw_get_keycode_from_keyboard_usage( + device, data[i] - 0x10); + break; + case 0xf8 ... 0xfc: /* Mouse button, release */ + type = RATBAG_MACRO_EVENT_KEY_RELEASED; + event_data = gskill_mouse_button_macro_code_to_keycode( + data[i]); + break; + case 0xfe: /* Scroll up */ + type = RATBAG_MACRO_EVENT_KEY_PRESSED; + event_data = KEY_SCROLLUP; + break; + } + + ratbag_button_macro_set_event(macro, event_idx, type, + event_data); + } + + return macro; +} + +/* FIXME: the macro struct here should be a const, but it looks like there's a + * couple of functions in ratbag that need to have a const qualifier added to + * their function declarations + */ +static struct gskill_macro_report * +gskill_macro_to_report(struct ratbag_device *device, + struct ratbag_button_macro *macro, + unsigned int profile, unsigned int button) +{ + struct gskill_data *drv_data = ratbag_get_drv_data(device); + struct gskill_macro_report *report = + &drv_data->profile_data[profile].macros[button]; + struct gskill_macro_delay *delay; + unsigned int event_num = ratbag_button_macro_get_num_events(macro); + struct ratbag_macro_event *event; + uint8_t *buf = report->macro_content; + int profile_pos, increment, event_idx; + ssize_t ret; + + memset(report, 0, sizeof(*report)); + + /* + * G.Skill's configuration software will cry if we don't have a name, + * so make sure we assign one + */ + if (!macro->macro.name || macro->macro.name[0] == '\0') { + ret = ratbag_utf8_to_enc(report->macro_name, + sizeof(report->macro_name), "UTF-16LE", + "Ratbag macro for profile %d button %d", + profile, button); + } else { + ret = ratbag_utf8_to_enc(report->macro_name, + sizeof(report->macro_name), "UTF-16LE", + "%s", macro->macro.name); + } + + if (ret < 0) + return NULL; + report->macro_name_length = ret; + + report->macro_num = (profile * 10) + button; + report->macro_exec_method = GSKILL_MACRO_METHOD_BUTTON_PRESS; + report->loop_count = 0; + report->please_set_me_to_1_thank_you = 1; /* No prob! Happy to help :) */ + + for (profile_pos = 0, increment = 1, event_idx = 0; + event_idx < (signed)event_num; + event_idx++, profile_pos += increment, increment = 1) { + event = ¯o->macro.events[event_idx]; + + switch (event->type) { + case RATBAG_MACRO_EVENT_WAIT: + delay = (struct gskill_macro_delay*)&buf[profile_pos]; + increment = sizeof(*delay); + + delay->tag = 1; + delay->count = event->event.timeout; + break; + case RATBAG_MACRO_EVENT_KEY_PRESSED: + case RATBAG_MACRO_EVENT_KEY_RELEASED: + buf[profile_pos] = gskill_macro_code_from_event(device, + event); + break; + case RATBAG_MACRO_EVENT_INVALID: + case RATBAG_MACRO_EVENT_NONE: + goto out; + } + } + +out: + report->macro_length = profile_pos; + + return report; +} + +static int +gskill_select_macro(struct ratbag_device *device, + unsigned profile, unsigned button, bool write) +{ + uint8_t macro_num = (profile * 10) + button; + uint8_t buf[GSKILL_REPORT_SIZE_CMD] = { GSKILL_GENERAL_CMD, 0xc4, 0x0b, + macro_num, write }; + int rc; + + /* + * Just like in gskill_select_profile(), we can't use the normal + * command handler for this + */ + rc = ratbag_hidraw_raw_request(device, GSKILL_GENERAL_CMD, buf, + sizeof(buf), HID_FEATURE_REPORT, + HID_REQ_SET_REPORT); + if (rc != sizeof(buf)) { + log_error(device->ratbag, + "Error while setting macro number to read/write: %d\n", + rc); + return rc < 0 ? rc : -EPROTO; + } + + return 0; +} + +static struct gskill_macro_report * +gskill_read_button_macro(struct ratbag_device *device, + unsigned int profile, unsigned int button) +{ + struct gskill_data *drv_data = ratbag_get_drv_data(device); + struct gskill_macro_report *report = + &drv_data->profile_data[profile].macros[button]; + uint8_t checksum; + int rc; + + rc = gskill_select_macro(device, profile, button, false); + if (rc) + return NULL; + + /* Wait for the device to be ready */ + msleep(100); + + rc = ratbag_hidraw_raw_request(device, GSKILL_GET_SET_MACRO, + (uint8_t*)report, sizeof(*report), + HID_FEATURE_REPORT, HID_REQ_GET_REPORT); + if (rc < (signed)sizeof(*report)) { + log_error(device->ratbag, + "Failed to retrieve macro for profile %d for button %d: %d\n", + profile, button, rc); + return NULL; + } + + checksum = gskill_calculate_checksum((uint8_t*)report, sizeof(*report)); + if (checksum != report->checksum) { + log_error(device->ratbag, + "Invalid checksum on macro for profile %d button %d\n", + profile, button); + return NULL; + } + + return report; +} + +static int +gskill_write_button_macro(struct ratbag_device *device, + struct gskill_macro_report *report) +{ + unsigned int profile = report->macro_num / 10; + unsigned int button = report->macro_num % 10; + int rc; + + rc = gskill_select_macro(device, profile, button, true); + if (rc) + return rc; + + /* Wait for the device to be ready */ + msleep(200); + + memset(&report->header, 0, sizeof(report->header)); + report->header.write.report_id = 0x4; + report->checksum = gskill_calculate_checksum((uint8_t*)report, + sizeof(*report)); + + rc = ratbag_hidraw_raw_request(device, GSKILL_GET_SET_MACRO, + (uint8_t*)report, sizeof(*report), + HID_FEATURE_REPORT, HID_REQ_SET_REPORT); + if (rc < 0) { + log_error(device->ratbag, + "Failed to write macro for profile %d button %d to mouse: %d\n", + profile, button, rc); + return rc; + } + + return 0; +} + +/* + * FIXME: Unfortunately if we ever try to get a profile that's above the + * current profile limit of the mouse, we end up making it cry and barf up + * garbage. No one likes barfing up garbage, so we unfortunately have to work + * around this until we have the ability to enable/disable profiles in ratbag. + */ +static int +gskill_workaround_profile_count(struct ratbag_device *device) +{ + int ret; + + ret = gskill_get_profile_count(device); + if (ret == GSKILL_PROFILE_MAX) + return 0; + if (ret < 0) + return ret; + + log_info(device->ratbag, + "We don't support dynamically enabling/disabling profiles yet, sorry! Setting profile count of mouse to 5\n"); + + ret = gskill_set_profile_count(device, GSKILL_PROFILE_MAX); + return ret; +} + +static int +gskill_probe(struct ratbag_device *device) +{ + struct gskill_data *drv_data = NULL; + struct ratbag_profile *profile; + unsigned int active_idx; + int ret; + + ret = ratbag_open_hidraw(device); + if (ret) + return ret; + + drv_data = zalloc(sizeof(*drv_data)); + ratbag_set_drv_data(device, drv_data); + + ret = gskill_workaround_profile_count(device); + if (ret) + goto err; + + ret = gskill_get_firmware_version(device); + if (ret < 0) + goto err; + + log_debug(device->ratbag, + "Firmware version: %d\n", ret); + + ratbag_device_init_profiles(device, GSKILL_PROFILE_MAX, GSKILL_NUM_DPI, + GSKILL_BUTTON_MAX); + + ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_QUERY_CONFIGURATION); + ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_BUTTON_KEY); + ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_BUTTON_MACROS); + + ret = gskill_get_active_profile_idx(device); + if (ret < 0) + goto err; + + active_idx = ret; + list_for_each(profile, &device->profiles, link) { + if (profile->index == active_idx) { + profile->is_active = true; + break; + } + } + + return 0; + +err: + if (drv_data) { + ratbag_set_drv_data(device, NULL); + free(drv_data); + } + + ratbag_close_hidraw(device); + return ret; +} + +static void +gskill_read_resolutions(struct ratbag_profile *profile, + struct gskill_profile_report *report) +{ + struct gskill_data *drv_data = ratbag_get_drv_data(profile->device); + struct gskill_profile_data *pdata = + &drv_data->profile_data[profile->index]; + struct ratbag_resolution *resolution; + int dpi_x, dpi_y, hz, i; + + log_debug(profile->device->ratbag, + "Profile %d: DPI count is %d\n", + profile->index, report->dpi_num); + + hz = GSKILL_MAX_POLLING_RATE / (report->polling_rate + 1); + + for (i = 0; i < report->dpi_num; i++) { + dpi_x = report->dpi_levels[i].x * GSKILL_DPI_UNIT; + dpi_y = report->dpi_levels[i].y * GSKILL_DPI_UNIT; + + resolution = ratbag_resolution_init(profile, i, dpi_x, dpi_y, + hz); + resolution->is_active = (i == report->current_dpi_level); + pdata->res_idx_to_dev_idx[i] = i; + + ratbag_resolution_set_cap(resolution, + RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION); + } +} + +static void +gskill_read_profile_name(struct ratbag_device *device, + struct gskill_profile_report *report) +{ + char *name; + int ret; + + ret = ratbag_utf8_from_enc(report->name, sizeof(report->name), + "UTF-16LE", &name); + if (ret) { + log_debug(device->ratbag, + "Couldn't read profile name? Error %d\n", ret); + return; + } + + log_debug(device->ratbag, + "Profile %d name: \"%s\"\n", + report->profile_num, name); + free(name); +} + +static void +gskill_read_profile(struct ratbag_profile *profile, unsigned int index) +{ + struct ratbag_device *device = profile->device; + struct gskill_data *drv_data = ratbag_get_drv_data(device); + struct gskill_profile_data *pdata = &drv_data->profile_data[index]; + struct gskill_profile_report *report = &pdata->report; + uint8_t checksum; + int rc, retries; + + /* + * There's a couple of situations where after various commands, the + * mouse will get confused and send the wrong profile. Keep trying + * until we get what we want. + * + * As well, getting the wrong profile is sometimes a sign from the + * mouse we're doing something wrong. + */ + for (retries = 0; retries < 3; retries++) { + rc = gskill_select_profile(device, index, false); + if (rc < 0) + return; + + /* Wait for the device to be ready */ + msleep(100); + + rc = ratbag_hidraw_raw_request(device, GSKILL_GET_SET_PROFILE, + (uint8_t*)report, + sizeof(*report), + HID_FEATURE_REPORT, + HID_REQ_GET_REPORT); + if (rc < (signed)sizeof(*report)) { + log_error(device->ratbag, + "Error while requesting profile: %d\n", rc); + return; + } + + if (report->profile_num == index) + break; + + log_debug(device->ratbag, + "Mouse send wrong profile %d instead of %d, retrying...\n", + profile->index, index); + } + + checksum = gskill_calculate_checksum((uint8_t*)report, sizeof(*report)); + if (checksum != report->checksum) { + log_error(device->ratbag, + "Warning: profile %d invalid checksum (expected %x, got %x)\n", + profile->index, report->checksum, checksum); + } + + gskill_read_resolutions(profile, report); + gskill_read_profile_name(device, report); +} + +static int +gskill_write_resolution_dpi(struct ratbag_resolution *resolution, + int dpi_x, int dpi_y) +{ + struct ratbag_profile *profile = resolution->profile; + struct ratbag_device *device = profile->device; + struct gskill_data *drv_data = ratbag_get_drv_data(device); + struct gskill_profile_data *pdata = + &drv_data->profile_data[profile->index]; + struct gskill_profile_report *report = &pdata->report; + unsigned int res_idx = resolution - profile->resolution.modes; + int i, rc; + + if ((dpi_x && dpi_y) && + (dpi_x < GSKILL_MIN_DPI || dpi_y < GSKILL_MIN_DPI || + dpi_x > GSKILL_MAX_DPI || dpi_y > GSKILL_MAX_DPI || + dpi_x % GSKILL_DPI_UNIT || dpi_y % GSKILL_DPI_UNIT)) + return -EINVAL; + + report->dpi_num = 0; + memset(&report->dpi_levels, 0, sizeof(report->dpi_levels)); + memset(&pdata->res_idx_to_dev_idx, 0, + sizeof(pdata->res_idx_to_dev_idx)); + + /* + * These mice start acting strange if we leave holes in the DPI levels. + * So only write and map the enabled DPIs, disabled DPIs will just be + * lost on exit + */ + for (i = 0; i < GSKILL_NUM_DPI; i++) { + struct ratbag_resolution *res = &profile->resolution.modes[i]; + struct gskill_raw_dpi_level *level = + &report->dpi_levels[report->dpi_num]; + + if (!res->dpi_x || !res->dpi_y) + continue; + + level->x = res->dpi_x / GSKILL_DPI_UNIT; + level->y = res->dpi_y / GSKILL_DPI_UNIT; + pdata->res_idx_to_dev_idx[i] = report->dpi_num; + + log_debug(device->ratbag, "Profile %d res %ld mapped to %d\n", + profile->index, res - profile->resolution.modes, + report->dpi_num); + + report->dpi_num++; + } + + rc = gskill_do_write_profile(device, report); + if (rc < 0) + return rc; + + /* + * The active resolution is now going to be the first resolution on the + * device + */ + pdata->report.current_dpi_level = 0; + for (i = 0; i < report->dpi_num; i++) { + struct ratbag_resolution *res = &profile->resolution.modes[i]; + uint8_t dev_idx = pdata->res_idx_to_dev_idx[i]; + + res->is_active = (dev_idx == 0); + } + + log_debug(device->ratbag, "Profile %d resolution count set to %d\n", + profile->index, report->dpi_num); + log_debug(device->ratbag, "Profile %d resolution %d set to %dx%dHz\n", + profile->index, res_idx, dpi_x, dpi_y); + + return 0; +} + +static int +gskill_write_profile(struct ratbag_profile *profile) +{ + struct ratbag_device *device = profile->device; + struct gskill_data *drv_data = ratbag_get_drv_data(device); + struct gskill_profile_data *pdata = + &drv_data->profile_data[profile->index]; + struct gskill_profile_report *report = &pdata->report; + int rc; + + rc = gskill_do_write_profile(device, report); + if (rc) + return rc; + + return 0; +} + +#if 0 +static int +gskill_reset_profile(struct ratbag_profile *profile) +{ + struct ratbag_device *device = profile->device; + uint8_t buf[GSKILL_REPORT_SIZE_CMD] = { GSKILL_GENERAL_CMD, 0xc4, 0x0a, + profile->index }; + int rc; + + rc = gskill_general_cmd(device, buf); + if (rc < 0) + return rc; + + log_debug(device->ratbag, "reset profile %d to factory defaults\n", + profile->index); + + return 0; +} +#endif + +static void +gskill_read_button(struct ratbag_button *button) +{ + struct ratbag_profile *profile = button->profile; + struct ratbag_device *device = profile->device; + struct gskill_data *drv_data = ratbag_get_drv_data(device); + struct gskill_profile_report *report = + &drv_data->profile_data[profile->index].report; + struct gskill_macro_report *macro_report; + struct ratbag_button_macro *macro; + struct gskill_button_cfg *bcfg; + struct ratbag_button_action *act = &button->action; + + button->type = gskill_button_type_mapping[button->index]; + bcfg = &report->btn_cfgs[button->index]; + + 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); + ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_MACRO); + + /* Parse any parameters that might accompany the action type */ + switch (bcfg->type) { + case GSKILL_BUTTON_FUNCTION_WHEEL: + act->type = RATBAG_BUTTON_ACTION_TYPE_SPECIAL; + + if (bcfg->params.wheel.direction == GSKILL_WHEEL_SCROLL_UP) + act->action.special = + RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_UP; + else + act->action.special = + RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_DOWN; + break; + case GSKILL_BUTTON_FUNCTION_MOUSE: + act->type = RATBAG_BUTTON_ACTION_TYPE_BUTTON; + + switch (bcfg->params.mouse.button_mask) { + case GSKILL_BTN_MASK_LEFT: + act->action.button = RATBAG_BUTTON_TYPE_LEFT; + break; + case GSKILL_BTN_MASK_RIGHT: + act->action.button = RATBAG_BUTTON_TYPE_RIGHT; + break; + case GSKILL_BTN_MASK_MIDDLE: + act->action.button = RATBAG_BUTTON_TYPE_MIDDLE; + break; + case GSKILL_BTN_MASK_SIDE: + act->action.button = RATBAG_BUTTON_TYPE_SIDE; + break; + case GSKILL_BTN_MASK_EXTRA: + act->action.button = RATBAG_BUTTON_TYPE_EXTRA; + break; + } + break; + case GSKILL_BUTTON_FUNCTION_KBD: + act->type = RATBAG_BUTTON_ACTION_TYPE_KEY; + act->action.key.key = + ratbag_hidraw_get_keycode_from_keyboard_usage( + device, bcfg->params.kbd.hid_code); + break; + case GSKILL_BUTTON_FUNCTION_CONSUMER: + act->type = RATBAG_BUTTON_ACTION_TYPE_KEY; + act->action.key.key = + ratbag_hidraw_get_keycode_from_consumer_usage( + device, bcfg->params.consumer.code); + break; + case GSKILL_BUTTON_FUNCTION_DPI_UP: + case GSKILL_BUTTON_FUNCTION_DPI_DOWN: + case GSKILL_BUTTON_FUNCTION_CYCLE_DPI_UP: + case GSKILL_BUTTON_FUNCTION_CYCLE_PROFILE_UP: + case GSKILL_BUTTON_FUNCTION_DISABLE: + *act = *gskill_button_function_to_action(bcfg->type); + break; + case GSKILL_BUTTON_FUNCTION_MACRO: + macro_report = gskill_read_button_macro(device, + button->profile->index, + button->index); + if (!macro_report) + goto err; + + macro = gskill_macro_from_report(device, + macro_report); + if (!macro) + goto err; + + act->type = RATBAG_BUTTON_ACTION_TYPE_MACRO; + ratbag_button_copy_macro(button, macro); + ratbag_button_macro_unref(macro); + break; + default: + break; + } + + return; + +err: + act->type = RATBAG_BUTTON_ACTION_TYPE_NONE; +} + +static int +gskill_write_button(struct ratbag_button *button, + const struct ratbag_button_action *action) +{ + struct ratbag_profile *profile = button->profile; + struct ratbag_device *device = profile->device; + struct ratbag_button_macro *macro = NULL; + struct gskill_data *drv_data = ratbag_get_drv_data(device); + struct gskill_profile_data *pdata = + &drv_data->profile_data[profile->index]; + struct gskill_button_cfg *bcfg = &pdata->report.btn_cfgs[button->index]; + uint16_t code = 0; + int rc; + + macro = container_of(action->macro, macro, macro); + memset(&bcfg->params, 0, sizeof(bcfg->params)); + + switch (action->type) { + case RATBAG_BUTTON_ACTION_TYPE_SPECIAL: + switch (action->action.special) { + case RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_UP: + bcfg->type = GSKILL_BUTTON_FUNCTION_WHEEL; + bcfg->params.wheel.direction = GSKILL_WHEEL_SCROLL_UP; + break; + case RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_DOWN: + bcfg->type = GSKILL_BUTTON_FUNCTION_WHEEL; + bcfg->params.wheel.direction = GSKILL_WHEEL_SCROLL_DOWN; + break; + case RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP: + case RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP: + case RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN: + case RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP: + case RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_UP: + case RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_DOWN: + bcfg->type = gskill_button_function_from_action(action); + break; + default: + return -EINVAL; + } + + break; + case RATBAG_BUTTON_ACTION_TYPE_BUTTON: + bcfg->type = GSKILL_BUTTON_FUNCTION_MOUSE; + + switch (action->action.button) { + case RATBAG_BUTTON_TYPE_LEFT: + bcfg->params.mouse.button_mask = GSKILL_BTN_MASK_LEFT; + break; + case RATBAG_BUTTON_TYPE_RIGHT: + bcfg->params.mouse.button_mask = GSKILL_BTN_MASK_RIGHT; + break; + case RATBAG_BUTTON_TYPE_MIDDLE: + bcfg->params.mouse.button_mask = GSKILL_BTN_MASK_MIDDLE; + break; + case RATBAG_BUTTON_TYPE_SIDE: + bcfg->params.mouse.button_mask = GSKILL_BTN_MASK_SIDE; + break; + case RATBAG_BUTTON_TYPE_EXTRA: + bcfg->params.mouse.button_mask = GSKILL_BTN_MASK_EXTRA; + break; + default: + return -EINVAL; + } + break; + case RATBAG_BUTTON_ACTION_TYPE_KEY: + code = ratbag_hidraw_get_keyboard_usage_from_keycode( + device, action->action.key.key); + if (code) { + bcfg->type = GSKILL_BUTTON_FUNCTION_KBD; + bcfg->params.kbd.hid_code = code; + } else { + code = ratbag_hidraw_get_consumer_usage_from_keycode( + device, action->action.key.key); + + bcfg->type = GSKILL_BUTTON_FUNCTION_CONSUMER; + bcfg->params.consumer.code = code; + } + break; + case RATBAG_BUTTON_ACTION_TYPE_MACRO: + bcfg->type = GSKILL_BUTTON_FUNCTION_MACRO; + gskill_write_button_macro( + device, gskill_macro_to_report(device, macro, + profile->index, + button->index)); + + break; + case RATBAG_BUTTON_ACTION_TYPE_NONE: + bcfg->type = GSKILL_BUTTON_FUNCTION_DISABLE; + break; + default: + break; + } + + rc = gskill_do_write_profile(device, &pdata->report); + if (rc < 0) + return rc; + + return 0; +} + +static void +gskill_remove(struct ratbag_device *device) +{ + ratbag_close_hidraw(device); + free(ratbag_get_drv_data(device)); +} + +struct ratbag_driver gskill_driver = { + .name = "G.Skill Ripjaws MX780", + .id = "gskill", + .probe = gskill_probe, + .remove = gskill_remove, + .read_profile = gskill_read_profile, + .write_profile = gskill_write_profile, + .set_active_profile = gskill_set_active_profile, + .write_resolution_dpi = gskill_write_resolution_dpi, + .read_button = gskill_read_button, + .write_button = gskill_write_button, +}; diff --git a/src/libratbag-private.h b/src/libratbag-private.h index 041a8e68..adabbbbd 100644 --- a/src/libratbag-private.h +++ b/src/libratbag-private.h @@ -418,6 +418,7 @@ struct ratbag_driver etekcity_driver; struct ratbag_driver hidpp20_driver; struct ratbag_driver hidpp10_driver; struct ratbag_driver roccat_driver; +struct ratbag_driver gskill_driver; struct ratbag_device* ratbag_device_new(struct ratbag *ratbag, struct udev_device *udev_device, diff --git a/src/libratbag.c b/src/libratbag.c index 086a774d..f556274a 100644 --- a/src/libratbag.c +++ b/src/libratbag.c @@ -534,6 +534,7 @@ ratbag_create_context(const struct ratbag_interface *interface, ratbag_register_driver(ratbag, &hidpp20_driver); ratbag_register_driver(ratbag, &hidpp10_driver); ratbag_register_driver(ratbag, &roccat_driver); + ratbag_register_driver(ratbag, &gskill_driver); return ratbag; }