1
0
forked from drath/libratbag
Files
libratbag-plus/src/asus.c
2023-09-19 18:17:46 +03:00

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