diff --git a/hwdb/70-libratbag-mouse.hwdb b/hwdb/70-libratbag-mouse.hwdb index 8097d319..e28216e2 100644 --- a/hwdb/70-libratbag-mouse.hwdb +++ b/hwdb/70-libratbag-mouse.hwdb @@ -95,6 +95,10 @@ mouse:usb:v046dpc249:name:*: RATBAG_HIDPP10_DPI=0:5700@23.53 RATBAG_HIDPP10_PROFILE=G500 +# G300 +mouse:usb:v046dpc246:name:*: + RATBAG_DRIVER=logitech_g300 + # G500 mouse:usb:v046dpc068:name:*: RATBAG_DRIVER=hidpp10 diff --git a/src/Makefile.am b/src/Makefile.am index 0d9219f8..116bc5d0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -11,6 +11,7 @@ libratbag_la_SOURCES = \ driver-etekcity.c \ driver-hidpp20.c \ driver-hidpp10.c \ + driver-logitech-g300.c \ driver-roccat.c \ driver-gskill.c \ driver-test.c \ diff --git a/src/driver-logitech-g300.c b/src/driver-logitech-g300.c new file mode 100644 index 00000000..6f921dbf --- /dev/null +++ b/src/driver-logitech-g300.c @@ -0,0 +1,494 @@ +/* + * Copyright © 2016 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 +#include +#include +#include +#include +#include +#include + +#include "libratbag-private.h" +#include "libratbag-hidraw.h" + +#define LOGITECH_G300_PROFILE_MAX 2 +#define LOGITECH_G300_BUTTON_MAX 8 +#define LOGITECH_G300_NUM_DPI 4 + +#define LOGITECH_G300_REPORT_ID_SET_ACTIVE_PROFILE 0xF0 +#define LOGITECH_G300_REPORT_ID_GET_ACTIVE_PROFILE 0xF1 +#define LOGITECH_G300_REPORT_ID_SET_ACTIVE_RESOLUTION 0xF0 +#define LOGITECH_G300_REPORT_ID_PROFILE_0 0xF3 +#define LOGITECH_G300_REPORT_ID_PROFILE_1 0xF4 +#define LOGITECH_G300_REPORT_ID_PROFILE_2 0xF5 + +#define LOGITECH_G300_REPORT_SIZE_ACTIVE_PROFILE 2 +#define LOGITECH_G300_REPORT_SIZE_PROFILE 35 + +struct logitech_g300_resolution { + uint8_t dpi :7; /* Range 1-10. dpi = 250*value */ + uint8_t is_default :1; +} __attribute__((packed)); + +struct logitech_g300_button { + uint8_t code; + uint8_t modifier; + uint8_t key; +} __attribute__((packed)); + +struct logitech_g300_profile_report { + uint8_t id; /* F3, F4, F5 */ + uint8_t led; /* 00=off, 01=red, 02=green, 03=yellow, 04=blue, 05=pink, 06=turquoise, 07=white */ + uint8_t frequency; /* 00=1000, 01=125, 02=250, 03=500 */ + struct logitech_g300_resolution dpi_levels[LOGITECH_G300_NUM_DPI]; + uint8_t unknown; /* dpi index for shift, but something else too */ + struct logitech_g300_button buttons[LOGITECH_G300_BUTTON_MAX + 1]; +} __attribute__((packed)); + +struct logitech_g300_profile_data { + struct logitech_g300_profile_report report; +}; + +struct logitech_g300_data { + struct logitech_g300_profile_data profile_data[LOGITECH_G300_PROFILE_MAX + 1]; +}; + +_Static_assert(sizeof(struct logitech_g300_profile_report) == LOGITECH_G300_REPORT_SIZE_PROFILE, + "Size of logitech_g300_profile_report is wrong"); + +struct logitech_g300_button_type_mapping { + uint8_t raw; + enum ratbag_button_type type; +}; + +static const struct logitech_g300_button_type_mapping logitech_g300_button_type_mapping[] = { + { 0, RATBAG_BUTTON_TYPE_LEFT }, + { 1, RATBAG_BUTTON_TYPE_RIGHT }, + { 2, RATBAG_BUTTON_TYPE_MIDDLE }, + { 3, RATBAG_BUTTON_TYPE_THUMB }, + { 4, RATBAG_BUTTON_TYPE_THUMB2 }, + { 5, RATBAG_BUTTON_TYPE_PINKIE }, + { 6, RATBAG_BUTTON_TYPE_PINKIE2 }, + { 7, RATBAG_BUTTON_TYPE_PROFILE_CYCLE_UP }, + { 8, RATBAG_BUTTON_TYPE_RESOLUTION_CYCLE_UP }, +}; + +static enum ratbag_button_type +logitech_g300_raw_to_button_type(uint8_t data) +{ + const struct logitech_g300_button_type_mapping *mapping; + + ARRAY_FOR_EACH(logitech_g300_button_type_mapping, mapping) { + if (mapping->raw == data) + return mapping->type; + } + + return RATBAG_BUTTON_TYPE_UNKNOWN; +} + +struct logitech_g300_button_mapping { + uint8_t raw; + struct ratbag_button_action action; +}; + +static struct logitech_g300_button_mapping logitech_g300_button_mapping[] = { + /* 0x00 is either key or unassigned. Must be handled separatly */ + { 0x01, BUTTON_ACTION_BUTTON(1) }, + { 0x02, BUTTON_ACTION_BUTTON(2) }, + { 0x03, BUTTON_ACTION_BUTTON(3) }, + { 0x04, BUTTON_ACTION_BUTTON(4) }, + { 0x05, BUTTON_ACTION_BUTTON(5) }, + { 0x0A, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP) }, + { 0x0B, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN) }, + { 0x0C, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP) }, + { 0x0D, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP) }, + { 0x0E, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_ALTERNATE) }, + { 0x0F, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DEFAULT) }, +}; + +static const struct ratbag_button_action* +logitech_g300_raw_to_button_action(uint8_t data) +{ + struct logitech_g300_button_mapping *mapping; + + ARRAY_FOR_EACH(logitech_g300_button_mapping, mapping) { + if (mapping->raw == data) + return &mapping->action; + } + + return NULL; +} + +static uint8_t +logitech_g300_button_action_to_raw(const struct ratbag_button_action *action) +{ + struct logitech_g300_button_mapping *mapping; + + ARRAY_FOR_EACH(logitech_g300_button_mapping, mapping) { + if (ratbag_button_action_match(&mapping->action, action)) + return mapping->raw; + } + + return 0; +} + +struct logitech_g300_frequency_mapping { + uint8_t raw; + unsigned int frequency; +}; + +static struct logitech_g300_frequency_mapping logitech_g300_frequency_mapping[] = { + { 0, 1000 }, + { 1, 125 }, + { 2, 250 }, + { 3, 500 }, +}; + +static unsigned int +logitech_g300_raw_to_frequency(uint8_t data) +{ + struct logitech_g300_frequency_mapping *mapping; + + ARRAY_FOR_EACH(logitech_g300_frequency_mapping, mapping) { + if (mapping->raw == data) + return mapping->frequency; + } + + return 0; +} + +static uint8_t +logitech_g300_frequency_to_raw(unsigned int frequency) +{ + struct logitech_g300_frequency_mapping *mapping; + + ARRAY_FOR_EACH(logitech_g300_frequency_mapping, mapping) { + if (mapping->frequency == frequency) + return mapping->raw; + } + + return 0; +} + +static int +logitech_g300_current_profile(struct ratbag_device *device) +{ + struct logitech_g300_data *drv_data = device->drv_data; + uint8_t buf[LOGITECH_G300_REPORT_SIZE_ACTIVE_PROFILE]; + int i, ret; + + ret = ratbag_hidraw_raw_request(device, LOGITECH_G300_REPORT_ID_GET_ACTIVE_PROFILE, buf, + sizeof(buf), HID_FEATURE_REPORT, HID_REQ_GET_REPORT); + + if (ret < 0) + return ret; + + if (ret != sizeof(buf)) + return -EIO; + + /* The current profile is identifed by its LED color code. */ + for (i = 0; i <= LOGITECH_G300_PROFILE_MAX; i++) { + struct logitech_g300_profile_report report; + + report = drv_data->profile_data[i].report; + if (report.led == buf[1]) + return i; + } + + return -EPROTO; +} + +static int +logitech_g300_set_current_profile(struct ratbag_device *device, unsigned int index) +{ + uint8_t buf[] = {LOGITECH_G300_REPORT_ID_SET_ACTIVE_PROFILE, 0x80 + index, 0x00, 0x00}; + int ret; + + if (index > LOGITECH_G300_PROFILE_MAX) + return -EINVAL; + + ret = ratbag_hidraw_raw_request(device, buf[0], buf, sizeof(buf), + HID_FEATURE_REPORT, HID_REQ_SET_REPORT); + + return ret == sizeof(buf) ? 0 : ret; +} + +#if 0 +static int +logitech_g300_set_current_resolution(struct ratbag_device *device, unsigned int index) +{ + uint8_t buf[] = {LOGITECH_G300_REPORT_ID_SET_ACTIVE_PROFILE, 0x40 + index*2, 0x00, 0x00}; + int ret; + + if (index >= LOGITECH_G300_NUM_DPI) + return -EINVAL; + + ret = ratbag_hidraw_raw_request(device, buf[0], buf, sizeof(buf), + HID_FEATURE_REPORT, HID_REQ_SET_REPORT); + + return ret == sizeof(buf) ? 0 : ret; +} +#endif + +static void +logitech_g300_read_profile(struct ratbag_profile *profile, unsigned int index) +{ + struct ratbag_device *device = profile->device; + struct logitech_g300_data *drv_data = device->drv_data; + struct logitech_g300_profile_data *pdata; + struct logitech_g300_profile_report *report; + struct ratbag_resolution *resolution; + unsigned int i, hz; + uint8_t report_id; + int rc; + + assert(index <= LOGITECH_G300_PROFILE_MAX); + + pdata = &drv_data->profile_data[index]; + report = &pdata->report; + + switch (index) { + case 0: report_id = LOGITECH_G300_REPORT_ID_PROFILE_0; break; + case 1: report_id = LOGITECH_G300_REPORT_ID_PROFILE_1; break; + case 2: report_id = LOGITECH_G300_REPORT_ID_PROFILE_2; break; + } + + rc = ratbag_hidraw_raw_request(device, report_id, + (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; + } + + hz = logitech_g300_raw_to_frequency(report->frequency); + + for (i = 0; i < profile->resolution.num_modes; i++) { + struct logitech_g300_resolution *res = + &report->dpi_levels[i]; + + resolution = &profile->resolution.modes[i]; + resolution->dpi_x = res->dpi * 250; + resolution->dpi_y = res->dpi * 250; + resolution->hz = hz; + resolution->is_default = res->is_default; + } +} + +static void +logitech_g300_read_button(struct ratbag_button *button) +{ + const struct ratbag_button_action *action; + struct ratbag_profile *profile = button->profile; + struct ratbag_device *device = profile->device; + struct logitech_g300_data *drv_data = device->drv_data; + struct logitech_g300_profile_data *pdata; + struct logitech_g300_profile_report *profile_report; + struct logitech_g300_button *button_report; + + pdata = &drv_data->profile_data[profile->index]; + profile_report = &pdata->report; + button_report = &profile_report->buttons[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); + + button->type = logitech_g300_raw_to_button_type(button->index); + + action = logitech_g300_raw_to_button_action(button_report->code); + if (action) { + button->action = *action; + } + else if (button_report->code == 0x00 && (button_report->modifier > 0x00 || button_report->key > 0x00)) + { + struct ratbag_button_action *key_action = &button->action; + + key_action->type = RATBAG_BUTTON_ACTION_TYPE_KEY; + key_action->action.key.key = ratbag_hidraw_get_keycode_from_keyboard_usage( + device, button_report->key); + } +} + +static int +logitech_g300_test_hidraw(struct ratbag_device *device) +{ + return ratbag_hidraw_has_report(device, LOGITECH_G300_REPORT_ID_GET_ACTIVE_PROFILE); +} + +static int +logitech_g300_probe(struct ratbag_device *device) +{ + int rc; + struct ratbag_profile *profile; + struct logitech_g300_data *drv_data = NULL; + int active_idx; + + rc = ratbag_find_hidraw(device, logitech_g300_test_hidraw); + if (rc) + goto err; + + drv_data = zalloc(sizeof(*drv_data)); + ratbag_set_drv_data(device, drv_data); + + /* profiles are 0-indexed */ + ratbag_device_init_profiles(device, + LOGITECH_G300_PROFILE_MAX + 1, + LOGITECH_G300_NUM_DPI, + LOGITECH_G300_BUTTON_MAX + 1); + + ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_SWITCHABLE_RESOLUTION); + ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_SWITCHABLE_PROFILE); + ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_BUTTON_KEY); + + active_idx = logitech_g300_current_profile(device); + + if (active_idx < 0) { + log_error(device->ratbag, + "Can't talk to the mouse: '%s' (%d)\n", + strerror(-active_idx), + active_idx); + rc = -ENODEV; + goto err; + } + + list_for_each(profile, &device->profiles, link) { + if (profile->index == (unsigned int)active_idx) { + profile->is_active = true; + break; + } + } + + log_raw(device->ratbag, + "'%s' is in profile %d\n", + ratbag_device_get_name(device), + profile->index); + + return 0; + +err: + free(drv_data); + ratbag_set_drv_data(device, NULL); + return rc; +} + +static int +logitech_g300_write_profile(struct ratbag_profile *profile) +{ + struct ratbag_device *device = profile->device; + struct logitech_g300_data *drv_data = device->drv_data; + struct logitech_g300_profile_data *pdata; + struct logitech_g300_profile_report *report; + struct ratbag_button *button; + uint8_t *buf; + unsigned int hz, i; + int rc; + + pdata = &drv_data->profile_data[profile->index]; + report = &pdata->report; + + /* The same hz is used for all resolutions */ + hz = profile->resolution.modes[0].hz; + report->frequency = logitech_g300_frequency_to_raw(hz); + + for (i = 0; i < profile->resolution.num_modes; i++) { + struct ratbag_resolution *resolution = &profile->resolution.modes[i]; + struct logitech_g300_resolution *res = &report->dpi_levels[i]; + + res->dpi = resolution->dpi_x / 250; + res->is_default = resolution->is_default; + } + + list_for_each(button, &profile->buttons, link) { + struct ratbag_button_action *action = &button->action; + struct logitech_g300_button *raw_button; + + if (!button->dirty) + continue; + + raw_button = &report->buttons[button->index]; + + raw_button->code = logitech_g300_button_action_to_raw(action); + raw_button->modifier = 0x00; + raw_button->key = 0x00; + if (action->type == RATBAG_BUTTON_ACTION_TYPE_KEY) { + raw_button->key = ratbag_hidraw_get_keyboard_usage_from_keycode( + device, action->action.key.key); + } + } + + buf = (uint8_t*)report; + + rc = ratbag_hidraw_raw_request(device, report->id, + buf, + sizeof(*report), + HID_FEATURE_REPORT, + HID_REQ_SET_REPORT); + + return rc; +} + +static int +logitech_g300_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 = logitech_g300_write_profile(profile); + if (rc) + return rc; + } + + return 0; +} + +static void +logitech_g300_remove(struct ratbag_device *device) +{ + ratbag_close_hidraw(device); + free(ratbag_get_drv_data(device)); +} + +struct ratbag_driver logitech_g300_driver = { + .name = "Logitech G300", + .id = "logitech_g300", + .probe = logitech_g300_probe, + .remove = logitech_g300_remove, + .read_profile = logitech_g300_read_profile, + .commit = logitech_g300_commit, + .set_active_profile = logitech_g300_set_current_profile, + .read_button = logitech_g300_read_button, +}; diff --git a/src/libratbag-private.h b/src/libratbag-private.h index 3f7e942e..1d1a60f0 100644 --- a/src/libratbag-private.h +++ b/src/libratbag-private.h @@ -433,6 +433,7 @@ log_buffer(struct ratbag *ratbag, struct ratbag_driver etekcity_driver; struct ratbag_driver hidpp20_driver; struct ratbag_driver hidpp10_driver; +struct ratbag_driver logitech_g300_driver; struct ratbag_driver roccat_driver; struct ratbag_driver gskill_driver; diff --git a/src/libratbag.c b/src/libratbag.c index 8965b5b5..dcf280e3 100644 --- a/src/libratbag.c +++ b/src/libratbag.c @@ -533,6 +533,7 @@ ratbag_create_context(const struct ratbag_interface *interface, ratbag_register_driver(ratbag, &etekcity_driver); ratbag_register_driver(ratbag, &hidpp20_driver); ratbag_register_driver(ratbag, &hidpp10_driver); + ratbag_register_driver(ratbag, &logitech_g300_driver); ratbag_register_driver(ratbag, &roccat_driver); ratbag_register_driver(ratbag, &gskill_driver);