Add driver for the Logitech G300/G300s

Supports the common functionality exposed by libratbag:
 - selecting profile 1-3 (profiles cannot be disabled)
 - selecting resolution 1-4 (can also not be disabled)
 - report frequency
 - default resolution per profile
 - button actions: mouse button numbers or key+modifier
   (modifiers are just a bit field and can easly be set
   in the driver, but not included as libratbag does not
   support it yet)
 - LEDs are not yet hooked up but are just a table of 7
   different predifined colors.
 - Seting active profile and resolution are both implemented
   but only profile activation is in use. The resolution
   activation code is commented until supported from libratbag
   itself.

The G300 has an unusual way of numbering buttons:
Left:1, Right:2, Middle: 3

The purpose of report type 0xF2 is not known.
This commit is contained in:
Thomas Hindoe Paaboel Andersen
2016-10-21 15:25:08 +02:00
committed by bentiss
parent 7d241642f9
commit 25294d9a63
5 changed files with 501 additions and 0 deletions

View File

@@ -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

View File

@@ -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 \

494
src/driver-logitech-g300.c Normal file
View File

@@ -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 <assert.h>
#include <errno.h>
#include <libevdev/libevdev.h>
#include <linux/input.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "libratbag-private.h"
#include "libratbag-hidraw.h"
#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,
};

View File

@@ -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;

View File

@@ -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);