forked from drath/libratbag
672 lines
19 KiB
C
672 lines
19 KiB
C
/*
|
|
* Copyright © 2018 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_G600_NUM_PROFILES 3
|
|
#define LOGITECH_G600_NUM_BUTTONS 20
|
|
#define LOGITECH_G600_NUM_DPI 4
|
|
#define LOGITECH_G600_NUM_LED 1
|
|
#define LOGITECH_G600_DPI_MIN 200
|
|
#define LOGITECH_G600_DPI_MAX 8200
|
|
|
|
#define LOGITECH_G600_REPORT_ID_GET_ACTIVE 0xF0
|
|
#define LOGITECH_G600_REPORT_ID_SET_ACTIVE 0xF0
|
|
#define LOGITECH_G600_REPORT_ID_PROFILE_0 0xF3
|
|
#define LOGITECH_G600_REPORT_ID_PROFILE_1 0xF4
|
|
#define LOGITECH_G600_REPORT_ID_PROFILE_2 0xF5
|
|
|
|
#define LOGITECH_G600_REPORT_SIZE_PROFILE 154
|
|
|
|
#define LOGITECH_G600_LED_SOLID 0x00
|
|
#define LOGITECH_G600_LED_BREATHE 0x01
|
|
#define LOGITECH_G600_LED_CYCLE 0x02
|
|
|
|
struct logitech_g600_button {
|
|
uint8_t code;
|
|
uint8_t modifier;
|
|
uint8_t key;
|
|
} __attribute__((packed));
|
|
|
|
struct logitech_g600_profile_report {
|
|
uint8_t id;
|
|
uint8_t led_red;
|
|
uint8_t led_green;
|
|
uint8_t led_blue;
|
|
uint8_t led_effect;
|
|
uint8_t led_duration;
|
|
uint8_t unknown1[5];
|
|
uint8_t frequency; /* frequency = 1000 / (value + 1) */
|
|
uint8_t dpi_shift; /* value is a linear range between 200->0x04, 8200->0xa4, so value * 50, 0x00 is disabled */
|
|
uint8_t dpi_default; /* between 1 and 4*/
|
|
uint8_t dpi[4]; /* value is a linear range between 200->0x04, 8200->0xa4, so value * 50, 0x00 is disabled */
|
|
uint8_t unknown2[13];
|
|
struct logitech_g600_button buttons[20];
|
|
uint8_t g_shift_color[3]; /* can't assign it in LGS, but the 3rd profile has one that shows the feature :) */
|
|
struct logitech_g600_button g_shift_buttons[20];
|
|
} __attribute__((packed));
|
|
|
|
struct logitech_g600_active_profile_report {
|
|
uint8_t id;
|
|
uint8_t unknown1 :1;
|
|
uint8_t resolution :2;
|
|
uint8_t unknown2 :1;
|
|
uint8_t profile :4;
|
|
uint8_t unknown3;
|
|
uint8_t unknown4;
|
|
} __attribute__((packed));
|
|
|
|
struct logitech_g600_profile_data {
|
|
struct logitech_g600_profile_report report;
|
|
};
|
|
|
|
struct logitech_g600_data {
|
|
struct logitech_g600_profile_data profile_data[LOGITECH_G600_NUM_PROFILES];
|
|
};
|
|
|
|
_Static_assert(sizeof(struct logitech_g600_profile_report) == LOGITECH_G600_REPORT_SIZE_PROFILE,
|
|
"Size of logitech_g600_profile_report is wrong");
|
|
|
|
struct logitech_g600_button_type_mapping {
|
|
uint8_t raw;
|
|
enum ratbag_button_type type;
|
|
};
|
|
|
|
static const struct logitech_g600_button_type_mapping logitech_g600_button_type_mapping[] = {
|
|
{ 0, RATBAG_BUTTON_TYPE_LEFT },
|
|
{ 1, RATBAG_BUTTON_TYPE_RIGHT },
|
|
{ 2, RATBAG_BUTTON_TYPE_MIDDLE },
|
|
{ 3, RATBAG_BUTTON_TYPE_WHEEL_LEFT },
|
|
{ 4, RATBAG_BUTTON_TYPE_WHEEL_RIGHT },
|
|
{ 5, RATBAG_BUTTON_TYPE_PINKIE },
|
|
{ 6, RATBAG_BUTTON_TYPE_PROFILE_CYCLE_UP },
|
|
{ 7, RATBAG_BUTTON_TYPE_RESOLUTION_CYCLE_UP },
|
|
{ 8, RATBAG_BUTTON_TYPE_SIDE }, // 8 = G9 ... 19 = G20
|
|
{ 9, RATBAG_BUTTON_TYPE_SIDE },
|
|
{ 10, RATBAG_BUTTON_TYPE_SIDE },
|
|
{ 11, RATBAG_BUTTON_TYPE_SIDE },
|
|
{ 12, RATBAG_BUTTON_TYPE_SIDE },
|
|
{ 13, RATBAG_BUTTON_TYPE_SIDE },
|
|
{ 14, RATBAG_BUTTON_TYPE_SIDE },
|
|
{ 15, RATBAG_BUTTON_TYPE_SIDE },
|
|
{ 16, RATBAG_BUTTON_TYPE_SIDE },
|
|
{ 17, RATBAG_BUTTON_TYPE_SIDE },
|
|
{ 18, RATBAG_BUTTON_TYPE_SIDE },
|
|
{ 19, RATBAG_BUTTON_TYPE_SIDE },
|
|
};
|
|
|
|
static enum ratbag_button_type
|
|
logitech_g600_raw_to_button_type(uint8_t data)
|
|
{
|
|
const struct logitech_g600_button_type_mapping *mapping;
|
|
|
|
ARRAY_FOR_EACH(logitech_g600_button_type_mapping, mapping) {
|
|
if (mapping->raw == data)
|
|
return mapping->type;
|
|
}
|
|
|
|
return RATBAG_BUTTON_TYPE_UNKNOWN;
|
|
}
|
|
|
|
struct logitech_g600_button_mapping {
|
|
uint8_t raw;
|
|
struct ratbag_button_action action;
|
|
};
|
|
|
|
static struct logitech_g600_button_mapping logitech_g600_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) },
|
|
{ 0x11, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP) },
|
|
{ 0x12, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN) },
|
|
{ 0x13, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP) },
|
|
{ 0x14, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP) },
|
|
{ 0x15, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_ALTERNATE) },
|
|
{ 0x17, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_SECOND_MODE) },
|
|
};
|
|
|
|
static const struct ratbag_button_action*
|
|
logitech_g600_raw_to_button_action(uint8_t data)
|
|
{
|
|
struct logitech_g600_button_mapping *mapping;
|
|
|
|
ARRAY_FOR_EACH(logitech_g600_button_mapping, mapping) {
|
|
if (mapping->raw == data)
|
|
return &mapping->action;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static uint8_t logitech_g600_modifier_to_raw(int modifier_flags)
|
|
{
|
|
uint8_t modifiers = 0x00;
|
|
|
|
if (modifier_flags & MODIFIER_LEFTCTRL)
|
|
modifiers |= 0x01;
|
|
if (modifier_flags & MODIFIER_LEFTSHIFT)
|
|
modifiers |= 0x02;
|
|
if (modifier_flags & MODIFIER_LEFTALT)
|
|
modifiers |= 0x04;
|
|
if (modifier_flags & MODIFIER_LEFTMETA)
|
|
modifiers |= 0x08;
|
|
if (modifier_flags & MODIFIER_RIGHTCTRL)
|
|
modifiers |= 0x10;
|
|
if (modifier_flags & MODIFIER_RIGHTSHIFT)
|
|
modifiers |= 0x20;
|
|
if (modifier_flags & MODIFIER_RIGHTALT)
|
|
modifiers |= 0x40;
|
|
if (modifier_flags & MODIFIER_RIGHTMETA)
|
|
modifiers |= 0x80;
|
|
|
|
return modifiers;
|
|
}
|
|
|
|
static int logitech_g600_raw_to_modifiers(uint8_t data)
|
|
{
|
|
int modifiers = 0;
|
|
|
|
if (data & 0x01)
|
|
modifiers |= MODIFIER_LEFTCTRL;
|
|
if (data & 0x02)
|
|
modifiers |= MODIFIER_LEFTSHIFT;
|
|
if (data & 0x04)
|
|
modifiers |= MODIFIER_LEFTALT;
|
|
if (data & 0x08)
|
|
modifiers |= MODIFIER_LEFTMETA;
|
|
if (data & 0x10)
|
|
modifiers |= MODIFIER_RIGHTCTRL;
|
|
if (data & 0x20)
|
|
modifiers |= MODIFIER_RIGHTSHIFT;
|
|
if (data & 0x40)
|
|
modifiers |= MODIFIER_RIGHTALT;
|
|
if (data & 0x80)
|
|
modifiers |= MODIFIER_RIGHTMETA;
|
|
|
|
return modifiers;
|
|
}
|
|
|
|
static uint8_t
|
|
logitech_g600_button_action_to_raw(const struct ratbag_button_action *action)
|
|
{
|
|
struct logitech_g600_button_mapping *mapping;
|
|
|
|
ARRAY_FOR_EACH(logitech_g600_button_mapping, mapping) {
|
|
if (ratbag_button_action_match(&mapping->action, action))
|
|
return mapping->raw;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
logitech_g600_get_active_profile_and_resolution(struct ratbag_device *device)
|
|
{
|
|
struct ratbag_profile *profile;
|
|
struct logitech_g600_active_profile_report buf;
|
|
int ret;
|
|
|
|
ret = ratbag_hidraw_raw_request(device, LOGITECH_G600_REPORT_ID_GET_ACTIVE,
|
|
(uint8_t*)&buf, sizeof(buf),
|
|
HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
|
|
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (ret != sizeof(buf))
|
|
return -EIO;
|
|
|
|
list_for_each(profile, &device->profiles, link) {
|
|
struct ratbag_resolution *resolution;
|
|
|
|
if (profile->index != buf.profile)
|
|
continue;
|
|
|
|
profile->is_active = true;
|
|
ratbag_profile_for_each_resolution(profile, resolution) {
|
|
resolution->is_active = resolution->index == buf.resolution;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
logitech_g600_set_current_resolution(struct ratbag_device *device, unsigned int index)
|
|
{
|
|
uint8_t buf[] = {LOGITECH_G600_REPORT_ID_SET_ACTIVE, 0x40 | (index << 1), 0x00, 0x00};
|
|
int ret;
|
|
|
|
log_debug(device->ratbag, "Setting active resolution to %d\n", index);
|
|
|
|
if (index >= LOGITECH_G600_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;
|
|
}
|
|
|
|
static int
|
|
logitech_g600_set_active_profile(struct ratbag_device *device, unsigned int index)
|
|
{
|
|
struct ratbag_profile *profile;
|
|
uint8_t buf[] = {LOGITECH_G600_REPORT_ID_SET_ACTIVE, 0x80 | (index << 4), 0x00, 0x00};
|
|
int ret, active_resolution = 0;
|
|
|
|
if (index >= LOGITECH_G600_NUM_PROFILES)
|
|
return -EINVAL;
|
|
|
|
ret = ratbag_hidraw_raw_request(device, buf[0], buf, sizeof(buf),
|
|
HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
|
|
|
|
if (ret != sizeof(buf))
|
|
return ret;
|
|
|
|
/* Update the active resolution. After profile change the default is used. */
|
|
list_for_each(profile, &device->profiles, link) {
|
|
struct ratbag_resolution *resolution;
|
|
|
|
if (profile->index != index)
|
|
continue;
|
|
|
|
ratbag_profile_for_each_resolution(profile, resolution) {
|
|
resolution->is_active = resolution->is_default;
|
|
|
|
if (resolution->is_active)
|
|
active_resolution = resolution->index;
|
|
}
|
|
}
|
|
|
|
ret = logitech_g600_set_current_resolution(device, active_resolution);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
logitech_g600_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_g600_data *drv_data = device->drv_data;
|
|
struct logitech_g600_profile_data *pdata;
|
|
struct logitech_g600_profile_report *profile_report;
|
|
struct logitech_g600_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);
|
|
ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_MACRO);
|
|
|
|
button->type = logitech_g600_raw_to_button_type(button->index);
|
|
|
|
action = logitech_g600_raw_to_button_action(button_report->code);
|
|
if (action) {
|
|
ratbag_button_set_action(button, action);
|
|
}
|
|
else if (button_report->code == 0x00 && (button_report->modifier > 0x00 || button_report->key > 0x00))
|
|
{
|
|
unsigned int key, modifiers;
|
|
int rc;
|
|
|
|
key = ratbag_hidraw_get_keycode_from_keyboard_usage(device,
|
|
button_report->key);
|
|
modifiers = logitech_g600_raw_to_modifiers(button_report->modifier);
|
|
|
|
rc = ratbag_button_macro_new_from_keycode(button, key, modifiers);
|
|
if (rc < 0) {
|
|
log_error(device->ratbag,
|
|
"Error while reading button %d\n",
|
|
button->index);
|
|
button->action.type = RATBAG_BUTTON_ACTION_TYPE_NONE;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
logitech_g600_read_led(struct ratbag_led *led)
|
|
{
|
|
struct ratbag_profile *profile = led->profile;
|
|
struct ratbag_device *device = profile->device;
|
|
struct logitech_g600_data *drv_data = device->drv_data;
|
|
struct logitech_g600_profile_data *pdata;
|
|
struct logitech_g600_profile_report *report;
|
|
|
|
pdata = &drv_data->profile_data[profile->index];
|
|
report = &pdata->report;
|
|
|
|
led->type = RATBAG_LED_TYPE_SIDE;
|
|
led->colordepth = RATBAG_LED_COLORDEPTH_RGB_888;
|
|
ratbag_led_set_mode_capability(led, RATBAG_LED_OFF);
|
|
ratbag_led_set_mode_capability(led, RATBAG_LED_ON);
|
|
ratbag_led_set_mode_capability(led, RATBAG_LED_BREATHING);
|
|
ratbag_led_set_mode_capability(led, RATBAG_LED_CYCLE);
|
|
|
|
switch (report->led_effect) {
|
|
case LOGITECH_G600_LED_SOLID:
|
|
led->mode = RATBAG_LED_ON;
|
|
break;
|
|
case LOGITECH_G600_LED_BREATHE:
|
|
led->mode = RATBAG_LED_BREATHING;
|
|
led->ms = report->led_duration * 1000;
|
|
break;
|
|
case LOGITECH_G600_LED_CYCLE:
|
|
led->mode = RATBAG_LED_CYCLE;
|
|
led->ms = report->led_duration * 1000;
|
|
break;
|
|
}
|
|
led->color.red = report->led_red;
|
|
led->color.green = report->led_green;
|
|
led->color.blue = report->led_blue;
|
|
}
|
|
|
|
static void
|
|
logitech_g600_read_profile(struct ratbag_profile *profile)
|
|
{
|
|
struct ratbag_device *device = profile->device;
|
|
struct logitech_g600_data *drv_data = device->drv_data;
|
|
struct logitech_g600_profile_data *pdata;
|
|
struct logitech_g600_profile_report *report;
|
|
struct ratbag_resolution *resolution;
|
|
unsigned int report_rates[] = { 125, 142, 166, 200, 250, 333, 500, 1000 };
|
|
uint8_t report_id;
|
|
int rc;
|
|
struct ratbag_button *button;
|
|
struct ratbag_led *led;
|
|
|
|
assert(profile->index < LOGITECH_G600_NUM_PROFILES);
|
|
|
|
pdata = &drv_data->profile_data[profile->index];
|
|
report = &pdata->report;
|
|
|
|
switch (profile->index) {
|
|
case 0: report_id = LOGITECH_G600_REPORT_ID_PROFILE_0; break;
|
|
case 1: report_id = LOGITECH_G600_REPORT_ID_PROFILE_1; break;
|
|
case 2: report_id = LOGITECH_G600_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 < (int)sizeof(*report)) {
|
|
log_error(device->ratbag,
|
|
"Error while requesting profile: %d\n", rc);
|
|
return;
|
|
}
|
|
|
|
|
|
ratbag_profile_for_each_resolution(profile, resolution) {
|
|
resolution->dpi_x = report->dpi[resolution->index] * 50;
|
|
resolution->dpi_y = report->dpi[resolution->index] * 50;
|
|
resolution->hz = 1000 / (report->frequency + 1);
|
|
resolution->is_default = report->dpi_default - 1U == resolution->index;
|
|
resolution->is_active = resolution->is_default;
|
|
|
|
ratbag_resolution_set_dpi_list_from_range(resolution,
|
|
LOGITECH_G600_DPI_MIN,
|
|
LOGITECH_G600_DPI_MAX);
|
|
ratbag_resolution_set_report_rate_list(resolution, report_rates,
|
|
ARRAY_LENGTH(report_rates));
|
|
}
|
|
|
|
ratbag_profile_for_each_button(profile, button)
|
|
logitech_g600_read_button(button);
|
|
|
|
ratbag_profile_for_each_led(profile, led)
|
|
logitech_g600_read_led(led);
|
|
|
|
log_debug(device->ratbag, "Unknown data in profile %d\n", profile->index);
|
|
log_buf_debug(device->ratbag, " profile->unknown1: ",
|
|
report->unknown1, 5);
|
|
log_buf_debug(device->ratbag, " profile->unknown2: ",
|
|
report->unknown2, 13);
|
|
}
|
|
|
|
static int
|
|
logitech_g600_test_hidraw(struct ratbag_device *device)
|
|
{
|
|
return ratbag_hidraw_has_report(device, LOGITECH_G600_REPORT_ID_GET_ACTIVE);
|
|
}
|
|
|
|
static int
|
|
logitech_g600_probe(struct ratbag_device *device)
|
|
{
|
|
int rc;
|
|
struct logitech_g600_data *drv_data = NULL;
|
|
struct ratbag_profile *profile;
|
|
|
|
rc = ratbag_find_hidraw(device, logitech_g600_test_hidraw);
|
|
if (rc)
|
|
goto err;
|
|
|
|
drv_data = zalloc(sizeof(*drv_data));
|
|
ratbag_set_drv_data(device, drv_data);
|
|
|
|
ratbag_device_init_profiles(device,
|
|
LOGITECH_G600_NUM_PROFILES,
|
|
LOGITECH_G600_NUM_DPI,
|
|
LOGITECH_G600_NUM_BUTTONS,
|
|
LOGITECH_G600_NUM_LED);
|
|
|
|
ratbag_device_for_each_profile(device, profile)
|
|
logitech_g600_read_profile(profile);
|
|
|
|
ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_RESOLUTION);
|
|
ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_SWITCHABLE_RESOLUTION);
|
|
ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_PROFILE);
|
|
ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_SWITCHABLE_PROFILE);
|
|
ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_BUTTON);
|
|
ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_BUTTON_KEY);
|
|
ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_BUTTON_MACROS);
|
|
|
|
rc = logitech_g600_get_active_profile_and_resolution(device);
|
|
|
|
if (rc < 0) {
|
|
log_error(device->ratbag,
|
|
"Can't talk to the mouse: '%s' (%d)\n",
|
|
strerror(-rc),
|
|
rc);
|
|
rc = -ENODEV;
|
|
goto err;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err:
|
|
free(drv_data);
|
|
ratbag_set_drv_data(device, NULL);
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
logitech_g600_write_profile(struct ratbag_profile *profile)
|
|
{
|
|
struct ratbag_device *device = profile->device;
|
|
struct logitech_g600_data *drv_data = device->drv_data;
|
|
struct logitech_g600_profile_data *pdata;
|
|
struct logitech_g600_profile_report *report;
|
|
struct ratbag_resolution *resolution;
|
|
struct ratbag_button *button;
|
|
struct ratbag_led *led;
|
|
|
|
uint8_t *buf;
|
|
int rc, active_resolution = 0;
|
|
|
|
pdata = &drv_data->profile_data[profile->index];
|
|
report = &pdata->report;
|
|
|
|
ratbag_profile_for_each_resolution(profile, resolution) {
|
|
report->dpi[resolution->index] = resolution->dpi_x / 50;
|
|
|
|
if (resolution->is_default)
|
|
report->dpi_default = resolution->index + 1;
|
|
|
|
if (profile->is_active && resolution->is_active)
|
|
active_resolution = resolution->index;
|
|
|
|
/* The same hz is used for all resolutions */
|
|
if (resolution->index == 0)
|
|
report->frequency = (1000 / resolution->hz) - 1;
|
|
}
|
|
|
|
list_for_each(button, &profile->buttons, link) {
|
|
struct ratbag_button_action *action = &button->action;
|
|
struct logitech_g600_button *raw_button;
|
|
|
|
raw_button = &report->buttons[button->index];
|
|
|
|
raw_button->code = logitech_g600_button_action_to_raw(action);
|
|
raw_button->modifier = 0x00;
|
|
raw_button->key = 0x00;
|
|
|
|
if (action->type == RATBAG_BUTTON_ACTION_TYPE_MACRO) {
|
|
unsigned int key, modifiers;
|
|
|
|
rc = ratbag_action_keycode_from_macro(action,
|
|
&key,
|
|
&modifiers);
|
|
if (rc < 0) {
|
|
log_error(device->ratbag,
|
|
"Error while writing macro for button %d\n",
|
|
button->index);
|
|
}
|
|
|
|
raw_button->key = ratbag_hidraw_get_keyboard_usage_from_keycode(
|
|
device, key);
|
|
raw_button->modifier = logitech_g600_modifier_to_raw(modifiers);
|
|
}
|
|
}
|
|
|
|
list_for_each(led, &profile->leds, link) {
|
|
report->led_red = led->color.red;
|
|
report->led_green = led->color.green;
|
|
report->led_blue = led->color.blue;
|
|
|
|
switch (led->mode) {
|
|
case RATBAG_LED_ON:
|
|
report->led_effect = LOGITECH_G600_LED_SOLID;
|
|
break;
|
|
case RATBAG_LED_OFF:
|
|
report->led_effect = LOGITECH_G600_LED_SOLID;
|
|
report->led_red = 0x00;
|
|
report->led_green = 0x00;
|
|
report->led_blue = 0x00;
|
|
break;
|
|
case RATBAG_LED_BREATHING:
|
|
report->led_effect = LOGITECH_G600_LED_BREATHE;
|
|
report->led_duration = led->ms / 1000;
|
|
break;
|
|
case RATBAG_LED_CYCLE:
|
|
report->led_effect = LOGITECH_G600_LED_CYCLE;
|
|
report->led_duration = led->ms / 1000;
|
|
break;
|
|
}
|
|
|
|
if (report->led_duration > 0x0f)
|
|
report->led_duration = 0x0f;
|
|
}
|
|
|
|
buf = (uint8_t*)report;
|
|
|
|
rc = ratbag_hidraw_raw_request(device, report->id,
|
|
buf,
|
|
sizeof(*report),
|
|
HID_FEATURE_REPORT,
|
|
HID_REQ_SET_REPORT);
|
|
|
|
if (rc < (int)sizeof(*report)) {
|
|
log_error(device->ratbag,
|
|
"Error while writing profile: %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (profile->is_active) {
|
|
rc = logitech_g600_set_current_resolution(device, active_resolution);
|
|
if (rc < 0)
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
logitech_g600_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_g600_write_profile(profile);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
logitech_g600_remove(struct ratbag_device *device)
|
|
{
|
|
ratbag_close_hidraw(device);
|
|
free(ratbag_get_drv_data(device));
|
|
}
|
|
|
|
struct ratbag_driver logitech_g600_driver = {
|
|
.name = "Logitech G600",
|
|
.id = "logitech_g600",
|
|
.probe = logitech_g600_probe,
|
|
.remove = logitech_g600_remove,
|
|
.commit = logitech_g600_commit,
|
|
.set_active_profile = logitech_g600_set_active_profile,
|
|
};
|