forked from drath/libratbag
483 lines
13 KiB
C
483 lines
13 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 "asus.h"
|
|
|
|
#include <assert.h>
|
|
|
|
#include "libratbag-data.h"
|
|
#include "libratbag-private.h"
|
|
|
|
/* ASUS commands */
|
|
#define ASUS_CMD_GET_LED_DATA 0x0312 /* get all LEDs */
|
|
#define ASUS_CMD_GET_SETTINGS 0x0412 /* dpi, rate, button response, angle snapping */
|
|
#define ASUS_CMD_GET_BUTTON_DATA 0x0512 /* get all buttons */
|
|
#define ASUS_CMD_GET_PROFILE_DATA 0x0012 /* get current profile info */
|
|
#define ASUS_CMD_SET_LED 0x2851 /* set single led */
|
|
#define ASUS_CMD_SET_SETTING 0x3151 /* dpi / rate / button response / angle snapping */
|
|
#define ASUS_CMD_SET_BUTTON 0x2151 /* set single button */
|
|
#define ASUS_CMD_SET_PROFILE 0x0250 /* switch profile */
|
|
#define ASUS_CMD_SAVE 0x0350 /* save settings */
|
|
|
|
/* fields order in _asus_dpiX_data, used for setting with ASUS_CMD_SET_SETTING */
|
|
#define ASUS_FIELD_RATE 0
|
|
#define ASUS_FIELD_RESPONSE 1
|
|
#define ASUS_FIELD_SNAPPING 2
|
|
|
|
/* key mapping, the index is actual ASUS code */
|
|
static const unsigned char ASUS_KEY_MAPPING[] = {
|
|
/* 00 */ 0, 0, 0, 0,
|
|
/* 04 */ KEY_A, KEY_B, KEY_C, KEY_D,
|
|
/* 08 */ KEY_E, KEY_F, KEY_G, KEY_H,
|
|
/* 0C */ KEY_I, KEY_J, KEY_K, KEY_L,
|
|
/* 0E */ KEY_M, KEY_N, KEY_O, KEY_P,
|
|
/* 14 */ KEY_Q, KEY_R, KEY_S, KEY_T,
|
|
/* 18 */ KEY_U, KEY_V, KEY_W, KEY_X,
|
|
/* 1C */ KEY_Y, KEY_Z, KEY_1, KEY_2,
|
|
/* 1E */ KEY_3, KEY_4, KEY_5, KEY_6,
|
|
/* 24 */ KEY_7, KEY_8, KEY_9, KEY_0,
|
|
/* 28 */ KEY_ENTER, KEY_ESC, KEY_BACKSPACE, KEY_TAB,
|
|
/* 2C */ KEY_SPACE, KEY_MINUS, KEY_KPPLUS, 0,
|
|
/* 2E */ 0, 0, 0, 0,
|
|
/* 34 */ 0, KEY_GRAVE, KEY_EQUAL, 0,
|
|
/* 38 */ KEY_SLASH, 0, KEY_F1, KEY_F2,
|
|
/* 3C */ KEY_F3, KEY_F4, KEY_F5, KEY_F6,
|
|
/* 3E */ KEY_F7, KEY_F8, KEY_F9, KEY_F10,
|
|
/* 44 */ KEY_F11, KEY_F12, 0, 0,
|
|
/* 48 */ 0, 0, KEY_HOME, KEY_PAGEUP,
|
|
/* 4C */ KEY_DELETE, 0, KEY_PAGEDOWN, KEY_RIGHT,
|
|
/* 4E */ KEY_LEFT, KEY_DOWN, KEY_UP, 0,
|
|
/* 54 */ 0, 0, 0, 0,
|
|
/* 58 */ 0, KEY_KP1, KEY_KP2, KEY_KP3,
|
|
/* 5C */ KEY_KP4, KEY_KP5, KEY_KP6, KEY_KP7,
|
|
/* 5E */ KEY_KP8, KEY_KP9, 0,
|
|
};
|
|
|
|
static unsigned int ASUS_POLLING_RATES[] = { 125, 250, 500, 1000 };
|
|
static unsigned int ASUS_DEBOUNCE_TIMES[] = { 4, 8, 12, 16, 20, 24, 28, 32 };
|
|
|
|
/* search for ASUS button by ratbag types */
|
|
const struct asus_button *
|
|
asus_find_button_by_action(struct ratbag_button_action action)
|
|
{
|
|
const struct asus_button *asus_button;
|
|
ARRAY_FOR_EACH(ASUS_BUTTON_MAPPING, asus_button) {
|
|
if ((action.type == RATBAG_BUTTON_ACTION_TYPE_BUTTON && asus_button->button == action.action.button) ||
|
|
(action.type == RATBAG_BUTTON_ACTION_TYPE_SPECIAL && asus_button->special == action.action.special))
|
|
return asus_button;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* search for ASUS button by ASUS button code */
|
|
const struct asus_button *
|
|
asus_find_button_by_code(uint8_t asus_code)
|
|
{
|
|
for (unsigned int i = 0; i < ARRAY_LENGTH(ASUS_BUTTON_MAPPING); i++) {
|
|
if (ASUS_BUTTON_MAPPING[i].asus_code == asus_code)
|
|
return &ASUS_BUTTON_MAPPING[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* search for ASUS key code by Linux key code */
|
|
int
|
|
asus_find_key_code(unsigned int linux_code)
|
|
{
|
|
for (unsigned int i = 0; i < ARRAY_LENGTH(ASUS_KEY_MAPPING); i++) {
|
|
if (ASUS_KEY_MAPPING[i] == linux_code)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
asus_get_linux_key_code(uint8_t asus_code) {
|
|
if (asus_code > ARRAY_LENGTH(ASUS_KEY_MAPPING)) {
|
|
return -1;
|
|
}
|
|
return ASUS_KEY_MAPPING[asus_code];
|
|
}
|
|
|
|
int
|
|
asus_query(struct ratbag_device *device,
|
|
union asus_request *request, union asus_response *response)
|
|
{
|
|
int rc;
|
|
|
|
rc = ratbag_hidraw_output_report(device, request->raw, ASUS_PACKET_SIZE);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
memset(response, 0, sizeof(union asus_response));
|
|
rc = ratbag_hidraw_read_input_report(device, response->raw, ASUS_PACKET_SIZE, NULL);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
/* invalid state, disconnected or sleeping */
|
|
if (response->data.code == ASUS_STATUS_ERROR) {
|
|
return ASUS_STATUS_ERROR;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
asus_setup_profile(struct ratbag_device *device, struct ratbag_profile *profile)
|
|
{
|
|
ratbag_profile_set_report_rate_list(
|
|
profile, ASUS_POLLING_RATES, ARRAY_LENGTH(ASUS_POLLING_RATES));
|
|
ratbag_profile_set_debounce_list(
|
|
profile, ASUS_DEBOUNCE_TIMES, ARRAY_LENGTH(ASUS_DEBOUNCE_TIMES));
|
|
}
|
|
|
|
void
|
|
asus_setup_button(struct ratbag_device *device, struct ratbag_button *button)
|
|
{
|
|
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);
|
|
}
|
|
|
|
void
|
|
asus_setup_resolution(struct ratbag_device *device, struct ratbag_resolution *resolution)
|
|
{
|
|
const struct dpi_range *dpirange = ratbag_device_data_asus_get_dpi_range(device->data);
|
|
if (!dpirange)
|
|
return;
|
|
|
|
ratbag_resolution_set_dpi_list_from_range(
|
|
resolution, dpirange->min, dpirange->max);
|
|
}
|
|
|
|
void
|
|
asus_setup_led(struct ratbag_device *device, struct ratbag_led *led)
|
|
{
|
|
led->colordepth = RATBAG_LED_COLORDEPTH_RGB_888;
|
|
ratbag_led_set_mode_capability(led, RATBAG_LED_ON);
|
|
ratbag_led_set_mode_capability(led, RATBAG_LED_CYCLE);
|
|
ratbag_led_set_mode_capability(led, RATBAG_LED_BREATHING);
|
|
}
|
|
|
|
int
|
|
asus_save_profile(struct ratbag_device *device)
|
|
{
|
|
int rc;
|
|
union asus_response response;
|
|
union asus_request request = {
|
|
.data.cmd = ASUS_CMD_SAVE,
|
|
};
|
|
|
|
rc = asus_query(device, &request, &response);
|
|
if (rc)
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
asus_get_profile_data(struct ratbag_device *device, struct asus_profile_data *data)
|
|
{
|
|
int rc;
|
|
uint32_t quirks = ratbag_device_data_asus_get_quirks(device->data);
|
|
union asus_response response;
|
|
union asus_request request = {
|
|
.data.cmd = ASUS_CMD_GET_PROFILE_DATA,
|
|
};
|
|
|
|
rc = asus_query(device, &request, &response);
|
|
if (rc)
|
|
return rc;
|
|
|
|
if (quirks & ASUS_QUIRK_STRIX_PROFILE)
|
|
data->profile_id = response.data.results[7];
|
|
else
|
|
data->profile_id = response.data.results[8];
|
|
|
|
data->version_primary_major = response.data.results[13];
|
|
data->version_primary_minor = response.data.results[12];
|
|
data->version_primary_build = response.data.results[11];
|
|
|
|
data->version_secondary_major = response.data.results[4];
|
|
data->version_secondary_minor = response.data.results[3];
|
|
data->version_secondary_build = response.data.results[2];
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
asus_set_profile(struct ratbag_device *device, unsigned int index)
|
|
{
|
|
int rc;
|
|
union asus_response response;
|
|
union asus_request request = {
|
|
.data.cmd = ASUS_CMD_SET_PROFILE,
|
|
.data.params[0] = index,
|
|
};
|
|
|
|
rc = asus_query(device, &request, &response);
|
|
if (rc)
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* read button bindings */
|
|
int
|
|
asus_get_binding_data(struct ratbag_device *device, union asus_binding_data *data)
|
|
{
|
|
int rc;
|
|
union asus_response response;
|
|
union asus_request request = {
|
|
.data.cmd = ASUS_CMD_GET_BUTTON_DATA,
|
|
};
|
|
|
|
rc = asus_query(device, &request, &response);
|
|
if (rc)
|
|
return rc;
|
|
|
|
memcpy(data->raw, response.raw, sizeof(data->raw));
|
|
return 0;
|
|
}
|
|
|
|
/* set button binding using ASUS code of the button */
|
|
int
|
|
asus_set_button_action(struct ratbag_device *device, uint8_t asus_code_src,
|
|
uint8_t asus_code_dst, uint8_t asus_type)
|
|
{
|
|
int rc;
|
|
union asus_response response;
|
|
union asus_request request = {
|
|
.data.cmd = ASUS_CMD_SET_BUTTON,
|
|
};
|
|
|
|
/* source (physical mouse button) */
|
|
request.data.params[2] = asus_code_src;
|
|
request.data.params[3] = ASUS_BUTTON_ACTION_TYPE_BUTTON;
|
|
|
|
/* destination (mouse button or keyboard key action) */
|
|
request.data.params[4] = asus_code_dst;
|
|
request.data.params[5] = asus_type;
|
|
|
|
rc = asus_query(device, &request, &response);
|
|
if (rc)
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
asus_get_resolution_data(struct ratbag_device *device, union asus_resolution_data *data)
|
|
{
|
|
int rc;
|
|
uint32_t quirks = ratbag_device_data_asus_get_quirks(device->data);
|
|
union asus_response response;
|
|
unsigned int dpi_count = ratbag_device_get_profile(device, 0)->num_resolutions;
|
|
unsigned int i;
|
|
union asus_request request = {
|
|
.data.cmd = ASUS_CMD_GET_SETTINGS,
|
|
};
|
|
|
|
rc = asus_query(device, &request, &response);
|
|
if (rc)
|
|
return rc;
|
|
|
|
memcpy(data->raw, response.raw, sizeof(data->raw));
|
|
|
|
/* convert DPI rates */
|
|
switch (dpi_count) {
|
|
case 2: /* 2 DPI presets */
|
|
for (i = 0; i < dpi_count; i++) {
|
|
data->data2.dpi[i] = data->data2.dpi[i] * 50 + 50;
|
|
if (quirks & ASUS_QUIRK_DOUBLE_DPI)
|
|
data->data2.dpi[i] *= 2;
|
|
}
|
|
data->data2.rate = ASUS_POLLING_RATES[data->data2.rate];
|
|
data->data2.response = ASUS_DEBOUNCE_TIMES[data->data2.response];
|
|
break;
|
|
|
|
case 4: /* 4 DPI presets */
|
|
for (i = 0; i < dpi_count; i++) {
|
|
data->data4.dpi[i] = data->data4.dpi[i] * 50 + 50;
|
|
if (quirks & ASUS_QUIRK_DOUBLE_DPI)
|
|
data->data4.dpi[i] *= 2;
|
|
}
|
|
data->data4.rate = ASUS_POLLING_RATES[data->data4.rate];
|
|
data->data4.response = ASUS_DEBOUNCE_TIMES[data->data4.response];
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* set DPI for the specified preset */
|
|
int
|
|
asus_set_dpi(struct ratbag_device *device, unsigned int index, unsigned int dpi)
|
|
{
|
|
int rc;
|
|
uint32_t quirks = ratbag_device_data_asus_get_quirks(device->data);
|
|
union asus_response response;
|
|
unsigned int idpi;
|
|
|
|
idpi = dpi;
|
|
if (quirks & ASUS_QUIRK_DOUBLE_DPI)
|
|
idpi /= 2;
|
|
|
|
union asus_request request = {
|
|
.data.cmd = ASUS_CMD_SET_SETTING,
|
|
.data.params[0] = index,
|
|
.data.params[2] = (idpi - 50) / 50,
|
|
};
|
|
|
|
rc = asus_query(device, &request, &response);
|
|
if (rc)
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* set polling rate in Hz */
|
|
int
|
|
asus_set_polling_rate(struct ratbag_device *device, unsigned int hz)
|
|
{
|
|
int rc;
|
|
union asus_response response;
|
|
unsigned int dpi_count = ratbag_device_get_profile(device, 0)->num_resolutions;
|
|
unsigned int i;
|
|
|
|
union asus_request request = {
|
|
.data.cmd = ASUS_CMD_SET_SETTING,
|
|
.data.params[0] = dpi_count + ASUS_FIELD_RATE, /* field index to set */
|
|
};
|
|
|
|
for (i = 0; i < ARRAY_LENGTH(ASUS_POLLING_RATES); i++) {
|
|
if (ASUS_POLLING_RATES[i] == hz) {
|
|
request.data.params[2] = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
rc = asus_query(device, &request, &response);
|
|
if (rc)
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* set button response/debounce in ms (from 4 to 32 with step of 4) */
|
|
int
|
|
asus_set_button_response(struct ratbag_device *device, unsigned int ms)
|
|
{
|
|
assert(ms >= 4);
|
|
|
|
int rc;
|
|
unsigned int dpi_count = ratbag_device_get_profile(device, 0)->num_resolutions;
|
|
union asus_response response;
|
|
unsigned int index = 0;
|
|
for (unsigned int i = 0; i < ARRAY_LENGTH(ASUS_DEBOUNCE_TIMES); i++) {
|
|
if (ASUS_DEBOUNCE_TIMES[i] == ms) {
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
union asus_request request = {
|
|
.data.cmd = ASUS_CMD_SET_SETTING,
|
|
.data.params[0] = dpi_count + ASUS_FIELD_RESPONSE, /* field index to set */
|
|
.data.params[2] = index,
|
|
};
|
|
|
|
rc = asus_query(device, &request, &response);
|
|
if (rc)
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
asus_set_angle_snapping(struct ratbag_device *device, bool is_enabled)
|
|
{
|
|
int rc;
|
|
union asus_response response;
|
|
unsigned int dpi_count = ratbag_device_get_profile(device, 0)->num_resolutions;
|
|
|
|
union asus_request request = {
|
|
.data.cmd = ASUS_CMD_SET_SETTING,
|
|
.data.params[0] = dpi_count + ASUS_FIELD_SNAPPING, /* field index to set */
|
|
.data.params[2] = is_enabled ? 1 : 0,
|
|
};
|
|
|
|
rc = asus_query(device, &request, &response);
|
|
if (rc)
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
asus_get_led_data(struct ratbag_device *device, union asus_led_data *data)
|
|
{
|
|
int rc;
|
|
union asus_response response;
|
|
union asus_request request = {
|
|
.data.cmd = ASUS_CMD_GET_LED_DATA,
|
|
};
|
|
|
|
rc = asus_query(device, &request, &response);
|
|
if (rc)
|
|
return rc;
|
|
|
|
memcpy(data->raw, response.raw, sizeof(data->raw));
|
|
return 0;
|
|
}
|
|
|
|
/* set LED mode, brightness (0-4) and color */
|
|
int
|
|
asus_set_led(struct ratbag_device *device,
|
|
uint8_t index, uint8_t mode, uint8_t brightness,
|
|
struct ratbag_color color)
|
|
{
|
|
int rc;
|
|
union asus_response response;
|
|
union asus_request request = {
|
|
.data.cmd = ASUS_CMD_SET_LED,
|
|
.data.params[0] = index,
|
|
.data.params[2] = mode,
|
|
.data.params[3] = brightness,
|
|
.data.params[4] = color.red,
|
|
.data.params[5] = color.green,
|
|
.data.params[6] = color.blue,
|
|
};
|
|
|
|
rc = asus_query(device, &request, &response);
|
|
if (rc)
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|