forked from drath/libratbag
555 lines
15 KiB
C
555 lines
15 KiB
C
/*
|
|
* Copyright (C) 2021 Kyoken, kyoken@kyoken.ninja
|
|
*
|
|
* 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 <math.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#include "libratbag-private.h"
|
|
#include "libratbag-hidraw.h"
|
|
#include "libratbag-data.h"
|
|
|
|
#include "asus.h"
|
|
|
|
/* ButtonMapping configuration property defaults */
|
|
static int ASUS_CONFIG_BUTTON_MAPPING[] = {
|
|
0xf0, /* left */
|
|
0xf1, /* right (button 3 in xev) */
|
|
0xf2, /* middle (button 2 in xev) */
|
|
0xe4, /* backward */
|
|
0xe5, /* forward */
|
|
0xe6, /* DPI */
|
|
0xe8, /* wheel up */
|
|
0xe9, /* wheel down */
|
|
-1, /* placeholder */
|
|
-1, /* placeholder */
|
|
-1, /* placeholder */
|
|
-1, /* placeholder */
|
|
-1, /* placeholder */
|
|
-1, /* placeholder */
|
|
-1, /* placeholder */
|
|
-1, /* placeholder */
|
|
-1, /* placeholder */
|
|
};
|
|
|
|
static unsigned int ASUS_LED_MODE[] = {
|
|
RATBAG_LED_ON,
|
|
RATBAG_LED_BREATHING,
|
|
RATBAG_LED_CYCLE,
|
|
RATBAG_LED_ON, /* rainbow wave */
|
|
RATBAG_LED_ON, /* reactive - react to clicks */
|
|
RATBAG_LED_ON, /* custom - depends on mouse type */
|
|
RATBAG_LED_ON, /* battery - battery indicator */
|
|
};
|
|
|
|
struct asus_data {
|
|
uint8_t is_ready;
|
|
int button_mapping[ASUS_MAX_NUM_BUTTON];
|
|
int button_indices[ASUS_MAX_NUM_BUTTON];
|
|
};
|
|
|
|
static int
|
|
asus_driver_load_profile(struct ratbag_device *device, struct ratbag_profile *profile)
|
|
{
|
|
int rc;
|
|
const struct _asus_binding *asus_binding;
|
|
const struct _asus_led *asus_led;
|
|
const struct asus_button *asus_button;
|
|
struct ratbag_button *button;
|
|
struct ratbag_led *led;
|
|
struct ratbag_resolution *resolution;
|
|
union asus_binding_data binding_data;
|
|
union asus_led_data led_data;
|
|
union asus_resolution_data resolution_data;
|
|
unsigned int dpi_count = ratbag_device_get_profile(device, 0)->num_resolutions;
|
|
struct asus_data *drv_data = ratbag_get_drv_data(device);
|
|
|
|
/* get buttons */
|
|
|
|
log_debug(device->ratbag, "Loading buttons data\n");
|
|
rc = asus_get_binding_data(device, &binding_data);
|
|
if (rc)
|
|
return rc;
|
|
|
|
ratbag_profile_for_each_button(profile, button) {
|
|
int asus_index = drv_data->button_indices[button->index];
|
|
if (asus_index == -1) {
|
|
log_debug(device->ratbag, "No mapping for button %d\n", button->index);
|
|
continue;
|
|
}
|
|
|
|
asus_binding = &binding_data.data.binding[asus_index];
|
|
|
|
/* disabled */
|
|
if (asus_binding->action == ASUS_BUTTON_CODE_DISABLED) {
|
|
button->action.type = RATBAG_BUTTON_ACTION_TYPE_NONE;
|
|
continue;
|
|
}
|
|
|
|
/* got action */
|
|
switch (asus_binding->type) {
|
|
case ASUS_BUTTON_ACTION_TYPE_KEY:
|
|
button->action.type = RATBAG_BUTTON_ACTION_TYPE_KEY;
|
|
/* ASUS code to Linux code */
|
|
button->action.action.key.key = asus_get_linux_key_code(asus_binding->action);
|
|
break;
|
|
|
|
case ASUS_BUTTON_ACTION_TYPE_BUTTON:
|
|
asus_button = asus_find_button_by_code(asus_binding->action);
|
|
if (asus_button != NULL) { /* found button to bind to */
|
|
button->action.type = asus_button->type;
|
|
if (asus_button->type == RATBAG_BUTTON_ACTION_TYPE_BUTTON)
|
|
button->action.action.button = asus_button->button;
|
|
else if (asus_button->type == RATBAG_BUTTON_ACTION_TYPE_SPECIAL)
|
|
button->action.action.special = asus_button->special;
|
|
} else {
|
|
log_debug(device->ratbag, "Unknown action code %02x\n", asus_binding->action);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* get DPIs */
|
|
|
|
log_debug(device->ratbag, "Loading resolutions data\n");
|
|
rc = asus_get_resolution_data(device, &resolution_data);
|
|
if (rc)
|
|
return rc;
|
|
|
|
switch (dpi_count) {
|
|
case 2: /* 2 DPI presets */
|
|
ratbag_profile_set_report_rate(profile, resolution_data.data2.rate);
|
|
ratbag_profile_for_each_resolution(profile, resolution)
|
|
ratbag_resolution_set_resolution(
|
|
resolution,
|
|
resolution_data.data2.dpi[resolution->index],
|
|
resolution_data.data2.dpi[resolution->index]);
|
|
break;
|
|
|
|
case 4: /* 4 DPI presets */
|
|
ratbag_profile_set_report_rate(profile, resolution_data.data4.rate);
|
|
ratbag_profile_for_each_resolution(profile, resolution)
|
|
ratbag_resolution_set_resolution(
|
|
resolution,
|
|
resolution_data.data4.dpi[resolution->index],
|
|
resolution_data.data4.dpi[resolution->index]);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* get LEDs */
|
|
|
|
log_debug(device->ratbag, "Loading LEDs data\n");
|
|
rc = asus_get_led_data(device, &led_data);
|
|
if (rc)
|
|
return rc;
|
|
|
|
ratbag_profile_for_each_led(profile, led) {
|
|
asus_led = &led_data.data.led[led->index];
|
|
led->mode = ASUS_LED_MODE[asus_led->mode];
|
|
/* convert brightness from 0-4 to 0-256 */
|
|
led->brightness = asus_led->brightness * 64;
|
|
led->color.red = asus_led->r;
|
|
led->color.green = asus_led->g;
|
|
led->color.blue = asus_led->b;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
asus_driver_save_profile(struct ratbag_device *device, struct ratbag_profile *profile)
|
|
{
|
|
int rc = 0;
|
|
struct ratbag_button *button;
|
|
struct ratbag_led *led;
|
|
struct ratbag_resolution *resolution;
|
|
struct asus_data *drv_data = ratbag_get_drv_data(device);
|
|
|
|
/* set buttons */
|
|
ratbag_profile_for_each_button(profile, button) {
|
|
if (!button->dirty)
|
|
continue;
|
|
|
|
int asus_index = drv_data->button_indices[button->index];
|
|
if (asus_index == -1) {
|
|
log_debug(device->ratbag, "No mapping for button %d\n", button->index);
|
|
continue;
|
|
}
|
|
|
|
const struct asus_button *asus_button;
|
|
uint8_t asus_code_src;
|
|
uint8_t asus_code_dst;
|
|
|
|
rc = drv_data->button_mapping[asus_index];
|
|
if (rc == -1) {
|
|
log_debug(device->ratbag, "No mapping for button %d\n", button->index);
|
|
continue;
|
|
}
|
|
|
|
asus_code_src = (uint8_t)rc;
|
|
log_debug(device->ratbag, "Button %d (%02x) changed\n",
|
|
button->index, asus_code_src);
|
|
|
|
switch (button->action.type) {
|
|
case RATBAG_BUTTON_ACTION_TYPE_NONE:
|
|
rc = asus_set_button_action(
|
|
device, asus_code_src, ASUS_BUTTON_CODE_DISABLED,
|
|
ASUS_BUTTON_ACTION_TYPE_BUTTON);
|
|
break;
|
|
|
|
case RATBAG_BUTTON_ACTION_TYPE_KEY:
|
|
/* Linux code to ASUS code */
|
|
rc = asus_find_key_code(button->action.action.key.key);
|
|
if (rc != -1) {
|
|
asus_code_dst = (uint8_t)rc;
|
|
rc = asus_set_button_action(
|
|
device, asus_code_src, asus_code_dst,
|
|
ASUS_BUTTON_ACTION_TYPE_KEY);
|
|
}
|
|
break;
|
|
|
|
case RATBAG_BUTTON_ACTION_TYPE_BUTTON:
|
|
case RATBAG_BUTTON_ACTION_TYPE_SPECIAL:
|
|
/* ratbag action to ASUS code */
|
|
asus_button = asus_find_button_by_action(button->action);
|
|
if (asus_button != NULL) /* found button to bind to */
|
|
rc = asus_set_button_action(
|
|
device, asus_code_src, asus_button->asus_code,
|
|
ASUS_BUTTON_ACTION_TYPE_BUTTON);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
|
|
/* set extra settings */
|
|
if (profile->rate_dirty) {
|
|
log_debug(device->ratbag, "Polling rate changed to %d Hz\n", profile->hz);
|
|
rc = asus_set_polling_rate(device, profile->hz);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
|
|
/* set DPIs */
|
|
ratbag_profile_for_each_resolution(profile, resolution) {
|
|
if (!resolution->dirty)
|
|
continue;
|
|
|
|
log_debug(
|
|
device->ratbag, "Resolution %d changed to %d\n",
|
|
resolution->index, resolution->dpi_x);
|
|
|
|
rc = asus_set_dpi(device, resolution->index, resolution->dpi_x);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
|
|
/* set LEDs */
|
|
ratbag_profile_for_each_led(profile, led) {
|
|
if (!led->dirty)
|
|
continue;
|
|
|
|
log_debug(device->ratbag, "LED %d changed\n", led->index);
|
|
uint8_t led_mode = 0;
|
|
for (unsigned int i = 0; i < ARRAY_LENGTH(ASUS_LED_MODE); i++) {
|
|
if (ASUS_LED_MODE[i] == led->mode) {
|
|
led_mode = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* convert brightness from 0-256 to 0-4 */
|
|
uint8_t led_brightness = (uint8_t)round((double)led->brightness / 64.0);
|
|
rc = asus_set_led(device, led->index, led_mode, led_brightness, led->color);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
asus_driver_load_profiles(struct ratbag_device *device)
|
|
{
|
|
int rc;
|
|
struct asus_profile_data profile_data;
|
|
struct ratbag_profile *profile;
|
|
unsigned int current_profile_id;
|
|
|
|
/* get current profile id */
|
|
rc = asus_get_profile_data(device, &profile_data);
|
|
if (rc)
|
|
return rc;
|
|
|
|
current_profile_id = profile_data.profile_id;
|
|
log_debug(device->ratbag, "Initial profile is %d\n", current_profile_id);
|
|
log_debug(
|
|
device->ratbag, "Primary version %02X.%02X.%02X\n",
|
|
profile_data.version_primary_major,
|
|
profile_data.version_primary_minor,
|
|
profile_data.version_primary_build);
|
|
log_debug(
|
|
device->ratbag, "Secondary version %02X.%02X.%02X\n",
|
|
profile_data.version_secondary_major,
|
|
profile_data.version_secondary_minor,
|
|
profile_data.version_secondary_build);
|
|
|
|
/* read ratbag profiles */
|
|
ratbag_device_for_each_profile(device, profile) {
|
|
if (profile->index == current_profile_id) { /* profile is already selected */
|
|
profile->is_active = true;
|
|
} else { /* switch profile */
|
|
profile->is_active = false;
|
|
|
|
log_debug(device->ratbag, "Switching to profile %d\n", profile->index);
|
|
rc = asus_set_profile(device, profile->index);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
|
|
rc = asus_driver_load_profile(device, profile);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
|
|
/* back to initial profile */
|
|
if (device->num_profiles > 1) {
|
|
log_debug(device->ratbag, "Switching back to initial profile %d\n", current_profile_id);
|
|
rc = asus_set_profile(device, current_profile_id);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
asus_driver_save_profiles(struct ratbag_device *device)
|
|
{
|
|
int rc;
|
|
struct asus_profile_data profile_data;
|
|
struct ratbag_profile *profile;
|
|
unsigned int current_profile_id;
|
|
|
|
/* get current profile id */
|
|
rc = asus_get_profile_data(device, &profile_data);
|
|
if (rc)
|
|
return rc;
|
|
|
|
current_profile_id = profile_data.profile_id;
|
|
log_debug(device->ratbag, "Initial profile is %d\n", current_profile_id);
|
|
|
|
ratbag_device_for_each_profile(device, profile) {
|
|
if (!profile->dirty)
|
|
continue;
|
|
|
|
log_debug(device->ratbag, "Profile %d changed\n", profile->index);
|
|
|
|
/* switch profile */
|
|
if (profile->index != current_profile_id) {
|
|
log_debug(device->ratbag, "Switching to profile %d\n", profile->index);
|
|
rc = asus_set_profile(device, profile->index);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
|
|
rc = asus_driver_save_profile(device, profile);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* save profile */
|
|
log_debug(device->ratbag, "Saving profile\n");
|
|
rc = asus_save_profile(device);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
|
|
/* back to initial profile */
|
|
if (device->num_profiles > 1) {
|
|
log_debug(device->ratbag, "Switching back to initial profile %d\n", current_profile_id);
|
|
rc = asus_set_profile(device, current_profile_id);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
asus_driver_probe(struct ratbag_device *device)
|
|
{
|
|
int rc;
|
|
unsigned int profile_count, dpi_count, button_count, led_count;
|
|
const struct asus_button *asus_button;
|
|
struct asus_data *drv_data;
|
|
struct ratbag_profile *profile;
|
|
struct ratbag_button *button;
|
|
struct ratbag_resolution *resolution;
|
|
struct ratbag_led *led;
|
|
|
|
rc = ratbag_open_hidraw(device);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* create device state data */
|
|
drv_data = zalloc(sizeof(*drv_data));
|
|
ratbag_set_drv_data(device, drv_data);
|
|
drv_data->is_ready = 1;
|
|
|
|
/* get device properties from configuration file */
|
|
profile_count = ratbag_device_data_asus_get_profile_count(device->data);
|
|
dpi_count = ratbag_device_data_asus_get_dpi_count(device->data);
|
|
button_count = ratbag_device_data_asus_get_button_count(device->data);
|
|
led_count = ratbag_device_data_asus_get_led_count(device->data);
|
|
const int *bm = ratbag_device_data_asus_get_button_mapping(device->data);
|
|
|
|
/* merge ButtonMapping configuration property with defaults */
|
|
for (unsigned int i = 0; i < ASUS_MAX_NUM_BUTTON; i++) {
|
|
drv_data->button_mapping[i] = bm[i] != -1 ? bm[i] : ASUS_CONFIG_BUTTON_MAPPING[i];
|
|
drv_data->button_indices[i] = -1;
|
|
}
|
|
|
|
/* setup a lookup table for all defined buttons */
|
|
unsigned int button_index = 0;
|
|
ARRAY_FOR_EACH(ASUS_BUTTON_MAPPING, asus_button) {
|
|
/* search for this button in the ButtonMapping by it's ASUS code */
|
|
for (unsigned int i = 0; i < ASUS_MAX_NUM_BUTTON; i++) {
|
|
if (drv_data->button_mapping[i] == (int)asus_button->asus_code) {
|
|
/* add button to indices array */
|
|
drv_data->button_indices[button_index] = (int)i;
|
|
log_debug(device->ratbag, "Button %d is mapped to 0x%02x\n",
|
|
button_index, (uint8_t)drv_data->button_mapping[i]);
|
|
button_index++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* init profiles */
|
|
ratbag_device_init_profiles(
|
|
device,
|
|
max(profile_count, 1),
|
|
max(dpi_count, 2),
|
|
max(button_count, 8),
|
|
max(led_count, 0));
|
|
|
|
/* setup profiles */
|
|
ratbag_device_for_each_profile(device, profile) {
|
|
if (profile->index == 0) {
|
|
profile->is_active = true;
|
|
}
|
|
|
|
asus_setup_profile(device, profile);
|
|
|
|
ratbag_profile_for_each_button(profile, button)
|
|
asus_setup_button(device, button);
|
|
|
|
ratbag_profile_for_each_resolution(profile, resolution)
|
|
asus_setup_resolution(device, resolution);
|
|
|
|
ratbag_profile_for_each_led(profile, led)
|
|
asus_setup_led(device, led);
|
|
}
|
|
|
|
/* load profiles */
|
|
rc = asus_driver_load_profiles(device);
|
|
if (rc == ASUS_STATUS_ERROR) { /* mouse in invalid state */
|
|
drv_data->is_ready = 0;
|
|
} else if (rc) { /* other errors */
|
|
log_error(
|
|
device->ratbag, "Can't talk to the mouse: '%s' (%d)\n",
|
|
strerror(-rc), rc);
|
|
free(drv_data);
|
|
ratbag_set_drv_data(device, NULL);
|
|
return -ENODEV;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
asus_driver_remove(struct ratbag_device *device)
|
|
{
|
|
ratbag_close_hidraw(device);
|
|
free(ratbag_get_drv_data(device));
|
|
}
|
|
|
|
static int
|
|
asus_driver_commit(struct ratbag_device *device)
|
|
{
|
|
int rc;
|
|
struct asus_data *drv_data;
|
|
|
|
/* check last device state */
|
|
drv_data = ratbag_get_drv_data(device);
|
|
if (!drv_data->is_ready) { /* device was not ready */
|
|
log_error(device->ratbag, "Device was not ready, trying to reload\n");
|
|
rc = asus_driver_load_profiles(device);
|
|
if (rc) {
|
|
log_error(device->ratbag, "Device reloading failed (%d)\n", rc);
|
|
if (rc != ASUS_STATUS_ERROR)
|
|
return rc;
|
|
} else {
|
|
drv_data->is_ready = 1;
|
|
log_error(device->ratbag, "Device was successfully reloaded\n");
|
|
}
|
|
return RATBAG_ERROR_DEVICE; /* fail in any case because we tried to rollback instead of commit */
|
|
}
|
|
|
|
/* save profiles */
|
|
rc = asus_driver_save_profiles(device);
|
|
if (rc) {
|
|
log_error(device->ratbag, "Commit failed (%d)\n", rc);
|
|
if (rc != ASUS_STATUS_ERROR)
|
|
return rc;
|
|
return RATBAG_ERROR_DEVICE;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct ratbag_driver asus_driver = {
|
|
.name = "ASUS",
|
|
.id = "asus",
|
|
.probe = asus_driver_probe,
|
|
.remove = asus_driver_remove,
|
|
.commit = asus_driver_commit,
|
|
.set_active_profile = asus_set_profile,
|
|
};
|