/* * 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_NUM_LED 0 #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; bool disable_leds_in_sleep:1; enum { GSKILL_LED_PROFILE_MODE_BACKGROUND = 0, GSKILL_LED_PROFILE_MODE_OTHER = 1, } led_profile_mode :1; uint8_t :8; /* 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 { uint8_t profile_count; struct gskill_profile_data profile_data[GSKILL_PROFILE_MAX]; }; static inline struct gskill_profile_data * profile_to_pdata(struct ratbag_profile *profile) { struct gskill_data *drv_data = profile->device->drv_data; return &drv_data->profile_data[profile->index]; } 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; } log_debug(device->ratbag, "Profile count: %d\n", buf[3]); 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; log_debug(device->ratbag, "Setting profile count to %d\n", count); 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_write_profile(struct ratbag_device *device, struct gskill_profile_report *report) { uint8_t *buf = (uint8_t*)report; int rc; /* * 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), "UTF-16LE", "Ratbag profile %d", report->profile_num); if (rc < 0) return rc; } report->checksum = gskill_calculate_checksum(buf, sizeof(*report)); 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; } 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) { ratbag_button_macro_unref(macro); 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; default: /* should never get there */ type = RATBAG_MACRO_EVENT_INVALID; event_data = 0; } 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; } static void gskill_read_resolutions(struct ratbag_profile *profile, struct gskill_profile_report *report) { struct gskill_profile_data *pdata = profile_to_pdata(profile); unsigned int rates[] = { 500, 1000 }; /* let's assume that is true */ 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); ratbag_profile_set_report_rate_list(profile, rates, ARRAY_LENGTH(rates)); ratbag_profile_set_report_rate(profile, hz); for (i = 0; i < report->dpi_num; i++) { _cleanup_resolution_ struct ratbag_resolution *resolution = NULL; dpi_x = report->dpi_levels[i].x * GSKILL_DPI_UNIT; dpi_y = report->dpi_levels[i].y * GSKILL_DPI_UNIT; resolution = ratbag_profile_get_resolution(profile, i); ratbag_resolution_set_resolution(resolution, dpi_x, dpi_y); 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); ratbag_resolution_set_dpi_list_from_range(resolution, GSKILL_MIN_DPI, GSKILL_MAX_DPI); } } 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 < 0) { 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) { struct ratbag_device *device = profile->device; struct gskill_data *drv_data = ratbag_get_drv_data(device); struct gskill_profile_data *pdata = profile_to_pdata(profile); struct gskill_profile_report *report = &pdata->report; uint8_t checksum; int rc, retries; if (profile->index >= drv_data->profile_count) { profile->is_enabled = false; return; } /* * 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, profile->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 == profile->index) break; log_debug(device->ratbag, "Mouse send wrong profile, retrying...\n"); } 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_update_resolutions(struct ratbag_profile *profile) { struct ratbag_device *device = profile->device; struct gskill_profile_data *pdata = profile_to_pdata(profile); struct gskill_profile_report *report = &pdata->report; int i; 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++) { _cleanup_resolution_ struct ratbag_resolution *res = NULL; struct gskill_raw_dpi_level *level = &report->dpi_levels[report->dpi_num]; res = ratbag_profile_get_resolution(profile, i); 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 %d mapped to %d\n", profile->index, res->index, report->dpi_num); report->dpi_num++; } 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_profile_report *report = &profile_to_pdata(profile)->report; struct gskill_macro_report *macro_report; struct ratbag_button_macro *macro; struct gskill_button_cfg *bcfg = &report->btn_cfgs[button->index]; struct ratbag_button_action *act = &button->action; button->type = gskill_button_type_mapping[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); /* * G.Skill mice can't save disabled profiles, so buttons from disabled * profiles shouldn't be set to anything */ if (!profile->is_enabled) { act->type = RATBAG_BUTTON_ACTION_TYPE_NONE; return; } /* 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_update_button(struct ratbag_button *button) { struct ratbag_profile *profile = button->profile; struct ratbag_device *device = profile->device; struct ratbag_button_action *action = &button->action; struct ratbag_button_macro *macro = NULL; struct gskill_profile_data *pdata = profile_to_pdata(profile); struct gskill_button_cfg *bcfg = &pdata->report.btn_cfgs[button->index]; uint16_t code = 0; 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; } return 0; } static int gskill_update_profile(struct ratbag_profile *profile) { struct ratbag_device *device = profile->device; struct ratbag_button *button; struct gskill_profile_data *pdata = profile_to_pdata(profile); struct gskill_profile_report *report = &pdata->report; int rc; gskill_update_resolutions(profile); list_for_each(button, &profile->buttons, link) { if (!button->dirty) continue; rc = gskill_update_button(button); if (rc) return rc; } rc = gskill_write_profile(device, report); if (rc) return rc; return 0; } 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_get_firmware_version(device); if (ret < 0) goto err; log_debug(device->ratbag, "Firmware version: %d\n", ret); ret = gskill_get_profile_count(device); if (ret < 0) goto err; drv_data->profile_count = ret; ratbag_device_init_profiles(device, GSKILL_PROFILE_MAX, GSKILL_NUM_DPI, GSKILL_BUTTON_MAX, GSKILL_NUM_LED); ret = gskill_get_active_profile_idx(device); if (ret < 0) goto err; active_idx = ret; ratbag_device_for_each_profile(device, profile) { struct ratbag_button *button; gskill_read_profile(profile); ratbag_profile_for_each_button(profile, button) gskill_read_button(button); ratbag_profile_set_cap(profile, RATBAG_PROFILE_CAP_DISABLE); if (profile->index == active_idx) profile->is_active = true; } return 0; err: if (drv_data) { ratbag_set_drv_data(device, NULL); free(drv_data); } ratbag_close_hidraw(device); return ret; } static int gskill_commit(struct ratbag_device *device) { struct ratbag_profile *profile; struct gskill_data *drv_data = ratbag_get_drv_data(device); struct gskill_profile_report *report; uint8_t profile_count = 0, new_idx, i; bool reload = false; int rc; /* * G.Skill mice only have a concept of how many profiles are enabled, * not which ones are and aren't enabled. So in order to provide the * ability to disable individual profiles we need to only write the * enabled profiles and make sure no holes are left inbetween profiles */ for (i = 0; i < GSKILL_PROFILE_MAX; i++) { profile = ratbag_device_get_profile(device, i); if (!profile->is_enabled) continue; report = &drv_data->profile_data[profile->index].report; new_idx = profile_count++; if (new_idx == report->profile_num) continue; log_debug(device->ratbag, "Profile %d remapped to %d\n", profile->index, new_idx); profile->dirty = true; report->profile_num = new_idx; } if (profile_count != drv_data->profile_count) { rc = gskill_set_profile_count(device, profile_count); if (rc < 0) return rc; drv_data->profile_count = profile_count; } list_for_each(profile, &device->profiles, link) { if (!profile->is_enabled || !profile->dirty) continue; log_debug(device->ratbag, "Profile %d changed, rewriting\n", profile->index); reload = true; rc = gskill_update_profile(profile); if (rc) return rc; } if (reload) { rc = gskill_reload_profile_data(device); if (rc) 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, .commit = gskill_commit, .set_active_profile = gskill_set_active_profile, };