forked from drath/libratbag
Add driver for Nubwo X7 Spectrum
Nubwo X7 Spectrum is a sinowealth-based gaming mouse. The sinowealth-nubwo driver included in this commit provides - LED support (except color mode that are not available in libratbag) - DPI and reporting support - NO button mapping/macros support
This commit is contained in:
5
data/devices/nubwo-x7-spectrum.device
Normal file
5
data/devices/nubwo-x7-spectrum.device
Normal file
@@ -0,0 +1,5 @@
|
||||
[Device]
|
||||
Name=Nubwo x7 spectrum
|
||||
DeviceMatch=usb:258a:0012
|
||||
Driver=sinowealth_nubwo
|
||||
|
||||
@@ -176,6 +176,7 @@ src_libratbag = [
|
||||
'src/driver-gskill.c',
|
||||
'src/driver-steelseries.c',
|
||||
'src/driver-sinowealth.c',
|
||||
'src/driver-sinowealth-nubwo.c',
|
||||
'src/driver-test.c',
|
||||
'src/libratbag.c',
|
||||
'src/libratbag.h',
|
||||
@@ -349,6 +350,7 @@ data_files = files(
|
||||
'data/devices/steelseries-sensei-raw.device',
|
||||
'data/devices/glorious-model-d.device',
|
||||
'data/devices/glorious-model-o.device',
|
||||
'data/devices/nubwo-x7-spectrum.device',
|
||||
)
|
||||
|
||||
install_data(data_files,
|
||||
|
||||
315
src/driver-sinowealth-nubwo.c
Normal file
315
src/driver-sinowealth-nubwo.c
Normal file
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
* Copyright © 2020 Pipat Saengow
|
||||
*
|
||||
* 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 "libratbag-enums.h"
|
||||
#include "libratbag-hidraw.h"
|
||||
#include "libratbag-private.h"
|
||||
#include "libratbag-util.h"
|
||||
#include "shared-macro.h"
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#define SINOWEALTHNUBWO_PERF_CMD_REPORTID 0x02
|
||||
#define SINOWEALTHNUBWO_AESTHETIC_CMD_REPORTID 0x03
|
||||
#define SINOWEALTHNUBWO_GET_FIRMWARE_CMD_REPORTID 0x04
|
||||
|
||||
#define SINOWEALTHNUBWO_GET_FIRMWARE_MSGSIZE 256
|
||||
#define SINOWEALTHNUBWO_GET_FIRMWARE_MSGOFFSET 48
|
||||
|
||||
#define SINOWEALTHNUBWO_PERF_CMD_MSGSIZE 16
|
||||
|
||||
// Actually more but I only implemented one
|
||||
#define SINOWEALTHNUBWO_NUM_PROFILES 1
|
||||
#define SINOWEALTHNUBWO_NUM_RESOLUTIONS 1
|
||||
// Actually 8 but I am not going to implement macros.
|
||||
#define SINOWEALTHNUBWO_NUM_BUTTONS 0
|
||||
#define SINOWEALTHNUBWO_NUM_LEDS 1
|
||||
|
||||
//Magic set_feature that must be called before requesting firmware string
|
||||
static uint8_t PREFIRMWARE_QUERY_MSG[] = {0x02, 0x01, 0x49, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
static unsigned int REPORT_RATES[] = { 125, 250, 333, 500, 1000 };
|
||||
static uint8_t REPORT_RATES_ENCODED[] = {0x08, 0x04, 0x03, 0x02, 0x01};
|
||||
static uint8_t REPORT_RATES_CMD[] = {0x02, 0x06, 0xbb, 0xaa, 0x28, 0x00, 0x01, 0x00};
|
||||
|
||||
static unsigned int DPILIST[] = { 1000, 2000, 3000, 5000, 15000};
|
||||
static uint8_t DPI_ENCODED[] = {0x04, 0x03, 0x02, 0x01, 0x00};
|
||||
static uint8_t DPI_CMD[] = { 0x02, 0x06, 0xbb, 0xaa, 0x32, 0x00, 0x01, 0x00 };
|
||||
|
||||
enum
|
||||
sinowealthnubwo_color_mode {
|
||||
SINOWEALTHNUBWO_COLOR_OFF = 0x00,
|
||||
SINOWEALTHNUBWO_COLOR_ON = 0x01,
|
||||
SINOWEALTHNUBWO_COLOR_BREATHING = 0x02,
|
||||
SINOWEALTHNUBWO_COLOR_COLOR_SHIFT = 0x03,
|
||||
SINOWEALTHNUBWO_COLOR_SPECTRUM = 0x04,
|
||||
SINOWEALTHNUBWO_COLOR_MARQUEE = 0x05,
|
||||
};
|
||||
|
||||
struct
|
||||
sinowealthnubwo_aesthetic_report {
|
||||
uint8_t report_id; //0x03
|
||||
uint8_t cmd[7];
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
uint8_t color_mode;
|
||||
uint8_t padzero; // 0x00
|
||||
uint8_t brightness; // 0x01 to 0x03
|
||||
uint8_t tempo; // 0x05 0x03 0x01
|
||||
uint8_t padzero2[16*3+1];
|
||||
} __packed__;
|
||||
|
||||
static uint8_t AESTHETIC_CMD[] = {0x06, 0xbb, 0xaa, 0x2a, 0x00, 0x0a, 0x00};
|
||||
|
||||
static int
|
||||
sinowealthnubwo_test_hidraw(struct ratbag_device *device)
|
||||
{
|
||||
return
|
||||
ratbag_hidraw_has_report(device, SINOWEALTHNUBWO_AESTHETIC_CMD_REPORTID)
|
||||
&& ratbag_hidraw_has_report(device, SINOWEALTHNUBWO_PERF_CMD_REPORTID)
|
||||
&& ratbag_hidraw_has_report(device, SINOWEALTHNUBWO_GET_FIRMWARE_CMD_REPORTID);
|
||||
}
|
||||
|
||||
static char*
|
||||
sinowealth_get_firmware_string(struct ratbag_device *device)
|
||||
{
|
||||
uint8_t buff[SINOWEALTHNUBWO_GET_FIRMWARE_MSGSIZE];
|
||||
int size;
|
||||
|
||||
size = ratbag_hidraw_set_feature_report(device, SINOWEALTHNUBWO_PERF_CMD_REPORTID, PREFIRMWARE_QUERY_MSG, ARRAY_LENGTH(PREFIRMWARE_QUERY_MSG));
|
||||
if (size < 0)
|
||||
log_error(device->ratbag, "Error while sending pre-firmware request message: %d\n", size);
|
||||
|
||||
size = ratbag_hidraw_get_feature_report(device, SINOWEALTHNUBWO_GET_FIRMWARE_CMD_REPORTID, buff, SINOWEALTHNUBWO_GET_FIRMWARE_MSGSIZE);
|
||||
if (size != SINOWEALTHNUBWO_GET_FIRMWARE_MSGSIZE)
|
||||
log_error(device->ratbag ,"Firmware report reply size mismatch expected %d got %d\n", SINOWEALTHNUBWO_GET_FIRMWARE_MSGSIZE, size);
|
||||
|
||||
char *fw_str = zalloc(SINOWEALTHNUBWO_GET_FIRMWARE_MSGSIZE); //Purposefully overallocate zeros to prevent buffer overrun.
|
||||
memcpy(fw_str, buff + SINOWEALTHNUBWO_GET_FIRMWARE_MSGOFFSET, SINOWEALTHNUBWO_GET_FIRMWARE_MSGSIZE - SINOWEALTHNUBWO_GET_FIRMWARE_MSGOFFSET);
|
||||
return fw_str;
|
||||
}
|
||||
|
||||
static int
|
||||
sinowealthnubwo_probe(struct ratbag_device *device)
|
||||
{
|
||||
int error;
|
||||
struct ratbag_profile *profile;
|
||||
struct ratbag_resolution *resolution;
|
||||
struct ratbag_led *led;
|
||||
|
||||
error = ratbag_find_hidraw(device, sinowealthnubwo_test_hidraw);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
ratbag_device_init_profiles(device,
|
||||
SINOWEALTHNUBWO_NUM_PROFILES,
|
||||
SINOWEALTHNUBWO_NUM_RESOLUTIONS,
|
||||
SINOWEALTHNUBWO_NUM_BUTTONS,
|
||||
SINOWEALTHNUBWO_NUM_LEDS);
|
||||
|
||||
ratbag_device_for_each_profile(device, profile) {
|
||||
profile->is_active = true;
|
||||
|
||||
ratbag_profile_set_cap(profile, RATBAG_PROFILE_CAP_WRITE_ONLY);
|
||||
ratbag_profile_set_report_rate_list(profile, REPORT_RATES, ARRAY_LENGTH(REPORT_RATES));
|
||||
ratbag_profile_for_each_resolution(profile, resolution) {
|
||||
ratbag_resolution_set_dpi_list(resolution, DPILIST, ARRAY_LENGTH(DPILIST));
|
||||
resolution->dpi_x = resolution->dpi_y = DPILIST[0];
|
||||
resolution->is_active = true;
|
||||
resolution->is_default = true;
|
||||
}
|
||||
ratbag_profile_for_each_led(profile, led) {
|
||||
led->mode = RATBAG_LED_OFF;
|
||||
led->color.red = led->color.green = led->color.blue = 0;
|
||||
led->colordepth = RATBAG_LED_COLORDEPTH_RGB_888;
|
||||
led->type = RATBAG_LED_TYPE_SIDE;
|
||||
led->color.red = led->color.green = led->color.blue = 0;
|
||||
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);
|
||||
//Actually more
|
||||
}
|
||||
}
|
||||
|
||||
char *fwstr = sinowealth_get_firmware_string(device);
|
||||
log_info(device->ratbag, "Firmware: %s\n", fwstr);
|
||||
free(fwstr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint8_t
|
||||
encode_dpi(unsigned int dpi)
|
||||
{
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(DPILIST); i++)
|
||||
if (DPILIST[i] == dpi)
|
||||
return DPI_ENCODED[i];
|
||||
return DPI_ENCODED[0];
|
||||
}
|
||||
|
||||
static uint8_t
|
||||
encode_report_rate(unsigned int reportrate)
|
||||
{
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(REPORT_RATES); i++)
|
||||
if (REPORT_RATES[i] == reportrate)
|
||||
return REPORT_RATES_ENCODED[i];
|
||||
return REPORT_RATES_ENCODED[0];
|
||||
}
|
||||
|
||||
static int
|
||||
sinowealthnubwo_set_dpi(struct ratbag_device *device, int dpi)
|
||||
{
|
||||
uint8_t buf[SINOWEALTHNUBWO_PERF_CMD_MSGSIZE];
|
||||
memset(buf, 0, SINOWEALTHNUBWO_PERF_CMD_MSGSIZE);
|
||||
memcpy(buf, DPI_CMD, ARRAY_LENGTH(DPI_CMD));
|
||||
buf[ARRAY_LENGTH(DPI_CMD)] = encode_dpi(dpi);
|
||||
int error = ratbag_hidraw_set_feature_report(device, SINOWEALTHNUBWO_PERF_CMD_REPORTID, buf, SINOWEALTHNUBWO_PERF_CMD_MSGSIZE);
|
||||
if (error < 0)
|
||||
return error;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
sinowealthnubwo_set_report_rate(struct ratbag_device *device, int reportrate)
|
||||
{
|
||||
uint8_t buf[SINOWEALTHNUBWO_PERF_CMD_MSGSIZE];
|
||||
memset(buf, 0, SINOWEALTHNUBWO_PERF_CMD_MSGSIZE);
|
||||
memcpy(buf, REPORT_RATES_CMD, ARRAY_LENGTH(REPORT_RATES_CMD));
|
||||
buf[ARRAY_LENGTH(REPORT_RATES_CMD)] = encode_report_rate(reportrate);
|
||||
int error = ratbag_hidraw_set_feature_report(device, SINOWEALTHNUBWO_PERF_CMD_REPORTID, buf, SINOWEALTHNUBWO_PERF_CMD_MSGSIZE);
|
||||
if (error < 0)
|
||||
return error;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static enum sinowealthnubwo_color_mode encode_color(enum ratbag_led_mode mode)
|
||||
{
|
||||
switch (mode) {
|
||||
case RATBAG_LED_OFF:
|
||||
return SINOWEALTHNUBWO_COLOR_OFF;
|
||||
case RATBAG_LED_ON:
|
||||
return SINOWEALTHNUBWO_COLOR_ON;
|
||||
case RATBAG_LED_CYCLE:
|
||||
return SINOWEALTHNUBWO_COLOR_MARQUEE;
|
||||
case RATBAG_LED_BREATHING:
|
||||
return SINOWEALTHNUBWO_COLOR_BREATHING;
|
||||
default:
|
||||
return SINOWEALTHNUBWO_COLOR_OFF;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t normalize_duration(int duration)
|
||||
{
|
||||
const int MAX_DURATION = 10000;
|
||||
const uint8_t avail_dura[] = {0x01, 0x03, 0x05};
|
||||
const int selected = (duration*((int) ARRAY_LENGTH(avail_dura)) -1)/MAX_DURATION;
|
||||
return avail_dura[selected];
|
||||
}
|
||||
|
||||
static uint8_t normalize_brightness(int brightness)
|
||||
{
|
||||
const int MAX_BRIGHTNESS = 255;
|
||||
return 1 + (brightness*3-1)/MAX_BRIGHTNESS;
|
||||
}
|
||||
|
||||
static int
|
||||
sinowealthnubwo_set_aesthetic(struct ratbag_device *device, struct ratbag_led *led) {
|
||||
struct sinowealthnubwo_aesthetic_report report;
|
||||
memset(&report, 0, sizeof(struct sinowealthnubwo_aesthetic_report));
|
||||
report.report_id = SINOWEALTHNUBWO_AESTHETIC_CMD_REPORTID;
|
||||
memcpy(report.cmd, AESTHETIC_CMD, ARRAY_LENGTH(AESTHETIC_CMD));
|
||||
report.r = led->color.red;
|
||||
report.g = led->color.green;
|
||||
report.b = led->color.blue;
|
||||
report.color_mode = encode_color(led->mode);
|
||||
report.tempo = normalize_duration(led->ms);
|
||||
report.brightness = normalize_brightness(led->brightness);
|
||||
|
||||
int size = ratbag_hidraw_set_feature_report(device, SINOWEALTHNUBWO_AESTHETIC_CMD_REPORTID, (uint8_t *) &report, sizeof(report));
|
||||
if (size < 0)
|
||||
return size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
sinowealthnubwo_write_profile(struct ratbag_device *device, struct ratbag_profile *profile)
|
||||
{
|
||||
struct ratbag_resolution *resolution;
|
||||
struct ratbag_led *led;
|
||||
int error;
|
||||
|
||||
log_debug(device->ratbag, "Writing updates\n");
|
||||
|
||||
log_debug(device->ratbag, "Setting report rate\n");
|
||||
error = sinowealthnubwo_set_report_rate(device, profile->hz);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
ratbag_profile_for_each_resolution(profile, resolution) {
|
||||
if (!resolution->dirty)
|
||||
continue;
|
||||
log_debug(device->ratbag, "Setting DPI\n");
|
||||
error = sinowealthnubwo_set_dpi(device, resolution->dpi_x);
|
||||
if (error)
|
||||
return error;
|
||||
}
|
||||
|
||||
ratbag_profile_for_each_led(profile, led) {
|
||||
if (!led->dirty)
|
||||
continue;
|
||||
log_debug(device->ratbag, "Setting aesthetic\n");
|
||||
error = sinowealthnubwo_set_aesthetic(device, led);
|
||||
if (error)
|
||||
return error;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
sinowealthnubwo_commit(struct ratbag_device *device)
|
||||
{
|
||||
struct ratbag_profile *profile;
|
||||
list_for_each(profile, &device->profiles, link) {
|
||||
if (!profile->dirty) continue;
|
||||
|
||||
int error = sinowealthnubwo_write_profile(device, profile);
|
||||
if (error)
|
||||
return error;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
sinowealthnubwo_remove(struct ratbag_device *device)
|
||||
{
|
||||
ratbag_close_hidraw(device);
|
||||
}
|
||||
|
||||
struct ratbag_driver sinowealth_nubwo_driver = {
|
||||
.name = "Sinowealth Nubwo",
|
||||
.id = "sinowealth_nubwo",
|
||||
.probe = sinowealthnubwo_probe,
|
||||
.remove = sinowealthnubwo_remove,
|
||||
.commit = sinowealthnubwo_commit,
|
||||
};
|
||||
@@ -52,6 +52,7 @@ enum driver {
|
||||
LOGITECH_G600,
|
||||
STEELSERIES,
|
||||
SINOWEALTH,
|
||||
SINOWEALTH_NUBWO,
|
||||
};
|
||||
|
||||
struct data_hidpp20 {
|
||||
@@ -269,6 +270,7 @@ static const struct driver_map {
|
||||
{ LOGITECH_G600, "logitech_g600", NULL},
|
||||
{ STEELSERIES, "steelseries", init_data_steelseries },
|
||||
{ SINOWEALTH, "sinowealth", NULL },
|
||||
{ SINOWEALTH_NUBWO, "sinowealth_nubwo", NULL},
|
||||
};
|
||||
|
||||
const char *
|
||||
|
||||
@@ -596,6 +596,7 @@ extern struct ratbag_driver roccat_driver;
|
||||
extern struct ratbag_driver gskill_driver;
|
||||
extern struct ratbag_driver steelseries_driver;
|
||||
extern struct ratbag_driver sinowealth_driver;
|
||||
extern struct ratbag_driver sinowealth_nubwo_driver;
|
||||
|
||||
struct ratbag_device*
|
||||
ratbag_device_new(struct ratbag *ratbag, struct udev_device *udev_device,
|
||||
|
||||
@@ -603,6 +603,7 @@ ratbag_create_context(const struct ratbag_interface *interface,
|
||||
ratbag_register_driver(ratbag, &gskill_driver);
|
||||
ratbag_register_driver(ratbag, &steelseries_driver);
|
||||
ratbag_register_driver(ratbag, &sinowealth_driver);
|
||||
ratbag_register_driver(ratbag, &sinowealth_nubwo_driver);
|
||||
|
||||
return ratbag;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user