marsgaming: implement Mars Gaming MM4 protocol

This commit is contained in:
Qjammer
2021-09-10 12:11:26 +02:00
committed by Yaroslav Chvanov
parent 70f2e96a66
commit 7fb3c63e8d
20 changed files with 1159 additions and 2 deletions

View File

@@ -0,0 +1,5 @@
[Device]
Name=Mars Gaming MM4
DeviceMatch=usb:04d9:fa58
DeviceType=mouse
Driver=marsgaming

View File

@@ -164,7 +164,7 @@ lur_mapfile = 'src/liblur.sym'
lur_version_flag = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), lur_mapfile)
lib_liblur = shared_library('lur',
src_liblur,
include_directories : include_directories('.'),
include_directories : include_directories('src'),
dependencies : deps_liblur,
version : liblur_so_version,
link_args : lur_version_flag,
@@ -205,6 +205,13 @@ src_libratbag = [
'src/driver-sinowealth.c',
'src/driver-sinowealth.h',
'src/driver-sinowealth-nubwo.c',
'src/driver-marsgaming/driver-marsgaming.c',
'src/driver-marsgaming/marsgaming-buttons.c',
'src/driver-marsgaming/marsgaming-command.c',
'src/driver-marsgaming/marsgaming-commit.c',
'src/driver-marsgaming/marsgaming-leds.c',
'src/driver-marsgaming/marsgaming-probe.c',
'src/driver-marsgaming/marsgaming-query.c',
'src/driver-test.c',
'src/libratbag.c',
'src/libratbag.h',
@@ -230,7 +237,7 @@ deps_libratbag = [
lib_libratbag = static_library('ratbag',
src_libratbag,
include_directories : include_directories('.'),
include_directories : include_directories('src'),
dependencies : deps_libratbag,
)

View File

@@ -0,0 +1,21 @@
#include "libratbag-private.h"
#include "marsgaming-probe.h"
#include "marsgaming-commit.h"
static void
marsgaming_remove(struct ratbag_device *device)
{
struct ratbag_profile *profile;
ratbag_device_for_each_profile(device, profile) {
free(profile->drv_data);
}
}
struct ratbag_driver marsgaming_driver = {
.name = "Mars Gaming",
.id = "marsgaming",
.probe = marsgaming_probe,
.commit = marsgaming_commit,
.remove = marsgaming_remove,
.set_active_profile = marsgaming_set_active_profile
};

View File

@@ -0,0 +1,391 @@
#include "marsgaming-buttons.h"
struct marsgaming_button_action_mapping {
enum marsgaming_button_action marsgaming_action_id;
struct ratbag_button_action ratbag_action;
};
/**
* This array contains only the actions that can be statically generated.
* This array is used in the marsgaming_button_action_lookup function.
* For any action that cannot be statically generated (has variables in it)
* we use the rest of the marsgaming_button_action_* functions
*/
static const struct marsgaming_button_action_mapping marsgaming_mm4_button_action_mapping[] = {
{ MARSGAMING_MM4_ACTION_LEFT_CLICK, BUTTON_ACTION_BUTTON(1) },
{ MARSGAMING_MM4_ACTION_RIGHT_CLICK, BUTTON_ACTION_BUTTON(2) },
{ MARSGAMING_MM4_ACTION_MIDDLE_CLICK, BUTTON_ACTION_BUTTON(3) },
{ MARSGAMING_MM4_ACTION_BACKWARD, BUTTON_ACTION_BUTTON(4) },
{ MARSGAMING_MM4_ACTION_FORWARD, BUTTON_ACTION_BUTTON(5) },
{ MARSGAMING_MM4_ACTION_DPI_SWITCH, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP) },
{ MARSGAMING_MM4_ACTION_DPI_MINUS, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN) },
{ MARSGAMING_MM4_ACTION_DPI_PLUS, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP) },
{ MARSGAMING_MM4_ACTION_PROFILE_SWITCH, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP) }
};
static struct ratbag_button_action
marsgaming_button_action_lookup(struct ratbag_button *button,
const struct marsgaming_button_info *button_info)
{
const struct marsgaming_button_action_mapping *button_mapping;
ARRAY_FOR_EACH(marsgaming_mm4_button_action_mapping, button_mapping) {
if (button_mapping->marsgaming_action_id == button_info->action)
return button_mapping->ratbag_action;
}
return (struct ratbag_button_action)BUTTON_ACTION_UNKNOWN;
}
static struct ratbag_button_action
marsgaming_button_action_media(struct ratbag_button *button,
const struct marsgaming_button_info *button_info)
{
// TODO: Convert from marsgaming media to ratbag media key codes
return (struct ratbag_button_action)BUTTON_ACTION_UNKNOWN;
}
static int
marsgaming_ratbag_button_macro_from_combo_keycode(struct ratbag_button *button, unsigned int key0, unsigned int key1, unsigned int modifiers);
static struct ratbag_button_action
marsgaming_button_action_key(struct ratbag_button *button,
const struct marsgaming_button_info *button_info)
{
// Single and combo keys share some structure, so we will treat them all like combo keys
const uint8_t mods = button_info->action_info.combo_key.modifiers;
const uint8_t key0 = button_info->action_info.combo_key.keys[0];
const uint8_t key1 = button_info->action_info.combo_key.keys[1];
const unsigned int event_key0 = ratbag_hidraw_get_keycode_from_keyboard_usage(button->profile->device, key0);
if (key1 == 0) {
ratbag_button_macro_new_from_keycode(button, event_key0, mods);
return button->action;
}
const unsigned int event_key1 = ratbag_hidraw_get_keycode_from_keyboard_usage(button->profile->device, key1);
marsgaming_ratbag_button_macro_from_combo_keycode(button, event_key0, event_key1, mods);
return button->action;
}
enum marsgaming_modifier_mask_offset {
MARSGAMING_MODIFIER_LEFTCTRL = 1 << 0,
MARSGAMING_MODIFIER_LEFTSHIFT = 1 << 1,
MARSGAMING_MODIFIER_LEFTALT = 1 << 2,
MARSGAMING_MODIFIER_LEFTMETA = 1 << 3,
MARSGAMING_MODIFIER_RIGHTCTRL = 1 << 4,
MARSGAMING_MODIFIER_RIGHTSHIFT = 1 << 5,
MARSGAMING_MODIFIER_RIGHTALT = 1 << 6,
MARSGAMING_MODIFIER_RIGHTMETA = 1 << 7,
};
struct marsgaming_modifier_mapping {
unsigned int modifier_mask;
unsigned int key;
};
static const struct marsgaming_modifier_mapping marsgaming_modifier_mapping[] = {
{ MARSGAMING_MODIFIER_LEFTCTRL, KEY_LEFTCTRL },
{ MARSGAMING_MODIFIER_LEFTSHIFT, KEY_LEFTSHIFT },
{ MARSGAMING_MODIFIER_LEFTALT, KEY_LEFTALT },
{ MARSGAMING_MODIFIER_LEFTMETA, KEY_LEFTMETA },
{ MARSGAMING_MODIFIER_RIGHTCTRL, KEY_RIGHTCTRL },
{ MARSGAMING_MODIFIER_RIGHTSHIFT, KEY_RIGHTSHIFT },
{ MARSGAMING_MODIFIER_RIGHTALT, KEY_RIGHTALT },
{ MARSGAMING_MODIFIER_RIGHTMETA, KEY_RIGHTMETA },
};
static int
marsgaming_ratbag_button_macro_from_combo_keycode(struct ratbag_button *button, unsigned int key0, unsigned int key1, unsigned int modifiers)
{
const struct marsgaming_modifier_mapping *mapping;
struct ratbag_button_macro *macro = ratbag_button_macro_new("combo-key");
int i = 0;
ARRAY_FOR_EACH(marsgaming_modifier_mapping, mapping) {
if (modifiers & mapping->modifier_mask) {
ratbag_button_macro_set_event(macro,
i++,
RATBAG_MACRO_EVENT_KEY_PRESSED,
mapping->key);
}
}
ratbag_button_macro_set_event(macro,
i++,
RATBAG_MACRO_EVENT_KEY_PRESSED,
key0);
ratbag_button_macro_set_event(macro,
i++,
RATBAG_MACRO_EVENT_KEY_PRESSED,
key1);
ratbag_button_macro_set_event(macro,
i++,
RATBAG_MACRO_EVENT_KEY_RELEASED,
key1);
ratbag_button_macro_set_event(macro,
i++,
RATBAG_MACRO_EVENT_KEY_RELEASED,
key0);
ARRAY_FOR_EACH(marsgaming_modifier_mapping, mapping) {
if (modifiers & mapping->modifier_mask) {
ratbag_button_macro_set_event(macro,
i++,
RATBAG_MACRO_EVENT_KEY_RELEASED,
mapping->key);
}
}
ratbag_button_copy_macro(button, macro);
ratbag_button_macro_unref(macro);
return 0;
}
static struct ratbag_button_action
marsgaming_button_action_macro(struct ratbag_button *button,
const struct marsgaming_button_info *button_info)
{
// TODO: Create a ratbag macro from the marsgaming macro
return (struct ratbag_button_action)BUTTON_ACTION_UNKNOWN;
}
static struct ratbag_button_action
marsgaming_button_action_fire(struct ratbag_button *button,
const struct marsgaming_button_info *button_info)
{
// There's no way to convert this to ratbag structs, so we'll return unknown
return (struct ratbag_button_action)BUTTON_ACTION_UNKNOWN;
}
#define MARSGAMING_BUTTON_ACTION_NONE \
{ \
.action = MARSGAMING_MM4_ACTION_MEDIA, .action_info.media.media_key = 0 \
}
static struct marsgaming_button_info
marsgaming_from_ratbag_to_action_none(struct ratbag_button *button)
{
return (struct marsgaming_button_info)MARSGAMING_BUTTON_ACTION_NONE;
}
struct marsgaming_from_ratbag_to_button_map {
uint8_t button_id;
struct marsgaming_button_info button_info;
};
static const struct marsgaming_from_ratbag_to_button_map marsgaming_from_ratbag_to_button_maps[] = {
{ 1, { .action = MARSGAMING_MM4_ACTION_LEFT_CLICK } },
{ 2, { .action = MARSGAMING_MM4_ACTION_RIGHT_CLICK } },
{ 3, { .action = MARSGAMING_MM4_ACTION_MIDDLE_CLICK } },
{ 4, { .action = MARSGAMING_MM4_ACTION_BACKWARD } },
{ 5, { .action = MARSGAMING_MM4_ACTION_FORWARD } },
};
static struct marsgaming_button_info
marsgaming_from_ratbag_to_action_button(struct ratbag_button *button)
{
const unsigned int button_id = button->action.action.button;
const struct marsgaming_from_ratbag_to_button_map *map;
ARRAY_FOR_EACH(marsgaming_from_ratbag_to_button_maps, map) {
if (button_id == map->button_id)
return map->button_info;
}
return (struct marsgaming_button_info)MARSGAMING_BUTTON_ACTION_NONE;
}
struct marsgaming_from_ratbag_to_special_map {
enum ratbag_button_action_special special_id;
struct marsgaming_button_info button_info;
};
static const struct marsgaming_from_ratbag_to_special_map marsgaming_from_ratbag_to_special_maps[] = {
{ RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP, { .action = MARSGAMING_MM4_ACTION_DPI_SWITCH } },
{ RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN, { .action = MARSGAMING_MM4_ACTION_DPI_MINUS } },
{ RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP, { .action = MARSGAMING_MM4_ACTION_DPI_PLUS } },
{ RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP, { .action = MARSGAMING_MM4_ACTION_PROFILE_SWITCH } },
};
static struct marsgaming_button_info
marsgaming_from_ratbag_to_action_special(struct ratbag_button *button)
{
const enum ratbag_button_action_special special_type = button->action.action.special;
const struct marsgaming_from_ratbag_to_special_map *map;
ARRAY_FOR_EACH(marsgaming_from_ratbag_to_special_maps, map) {
if (special_type == map->special_id)
return map->button_info;
}
return (struct marsgaming_button_info)MARSGAMING_BUTTON_ACTION_NONE;
}
static int
marsgaming_keycodes_from_ratbag_macro(struct ratbag_button_action *action, unsigned int *key0_out, unsigned int *key1_out, unsigned int *mods_out);
static struct marsgaming_button_info
marsgaming_from_ratbag_to_action_macro(struct ratbag_button *button)
{
unsigned int key0;
unsigned int key1;
unsigned int mods;
int e = marsgaming_keycodes_from_ratbag_macro(&button->action, &key0, &key1, &mods);
if (e == 1 || e == 2) { // We can convert this to 2-key combo
return (struct marsgaming_button_info){
.action = MARSGAMING_MM4_ACTION_COMBO_KEY,
.action_info.combo_key = {
.modifiers = mods,
.keys[0] = ratbag_hidraw_get_keyboard_usage_from_keycode(button->profile->device, key0),
.keys[1] = ratbag_hidraw_get_keyboard_usage_from_keycode(button->profile->device, key1),
}
};
}
// TODO: Implement macros
return (struct marsgaming_button_info)MARSGAMING_BUTTON_ACTION_NONE;
}
static int
marsgaming_keycodes_from_ratbag_macro(struct ratbag_button_action *action, unsigned int *key0_out, unsigned int *key1_out, unsigned int *modifiers_out)
{
struct ratbag_macro *macro = action->macro;
unsigned int key0 = KEY_RESERVED;
unsigned int key1 = KEY_RESERVED;
unsigned int modifiers = 0;
unsigned int i;
unsigned int keys_pressed = 0;
if (!macro || action->type != RATBAG_BUTTON_ACTION_TYPE_MACRO)
return -EINVAL;
if (macro->events[0].type == RATBAG_MACRO_EVENT_NONE)
return -EINVAL;
{
unsigned int num_keys = ratbag_action_macro_num_keys(action);
if (num_keys == 0 || num_keys > 2)
return -EINVAL;
}
for (i = 0; i < MAX_MACRO_EVENTS; i++) {
struct ratbag_macro_event event;
event = macro->events[i];
switch (event.type) {
case RATBAG_MACRO_EVENT_INVALID:
return -EINVAL;
case RATBAG_MACRO_EVENT_NONE:
return 0;
case RATBAG_MACRO_EVENT_KEY_PRESSED:
switch (event.event.key) {
case KEY_LEFTCTRL: modifiers |= MARSGAMING_MODIFIER_LEFTCTRL; break;
case KEY_LEFTSHIFT: modifiers |= MARSGAMING_MODIFIER_LEFTSHIFT; break;
case KEY_LEFTALT: modifiers |= MARSGAMING_MODIFIER_LEFTALT; break;
case KEY_LEFTMETA: modifiers |= MARSGAMING_MODIFIER_LEFTMETA; break;
case KEY_RIGHTCTRL: modifiers |= MARSGAMING_MODIFIER_RIGHTCTRL; break;
case KEY_RIGHTSHIFT: modifiers |= MARSGAMING_MODIFIER_RIGHTSHIFT; break;
case KEY_RIGHTALT: modifiers |= MARSGAMING_MODIFIER_RIGHTALT; break;
case KEY_RIGHTMETA: modifiers |= MARSGAMING_MODIFIER_RIGHTMETA; break;
default:
if (key0 == KEY_RESERVED) {
key0 = event.event.key;
} else if (key1 == KEY_RESERVED) {
key1 = event.event.key;
} else {
return -EINVAL;
}
++keys_pressed;
}
break;
case RATBAG_MACRO_EVENT_KEY_RELEASED:
switch (event.event.key) {
case KEY_LEFTCTRL: modifiers &= ~MARSGAMING_MODIFIER_LEFTCTRL; break;
case KEY_LEFTSHIFT: modifiers &= ~MARSGAMING_MODIFIER_LEFTSHIFT; break;
case KEY_LEFTALT: modifiers &= ~MARSGAMING_MODIFIER_LEFTALT; break;
case KEY_LEFTMETA: modifiers &= ~MARSGAMING_MODIFIER_LEFTMETA; break;
case KEY_RIGHTCTRL: modifiers &= ~MARSGAMING_MODIFIER_RIGHTCTRL; break;
case KEY_RIGHTSHIFT: modifiers &= ~MARSGAMING_MODIFIER_RIGHTSHIFT; break;
case KEY_RIGHTALT: modifiers &= ~MARSGAMING_MODIFIER_RIGHTALT; break;
case KEY_RIGHTMETA: modifiers &= ~MARSGAMING_MODIFIER_RIGHTMETA; break;
default:
// As soon as we release a key that we pressed, return what we have processed so far
if (event.event.key == key0 || event.event.key == key1) {
*key0_out = key0;
*key1_out = key1;
*modifiers_out = modifiers;
return (int)keys_pressed;
}
return -EINVAL;
}
case RATBAG_MACRO_EVENT_WAIT:
break;
default:
return -EINVAL;
}
}
return -EINVAL;
}
struct marsgaming_button_action_to_ratbag_parser {
enum marsgaming_button_action marsgaming_action_id;
struct ratbag_button_action (*parse_action)(struct ratbag_button *button,
const struct marsgaming_button_info *button_info);
};
static const struct marsgaming_button_action_to_ratbag_parser marsgaming_button_action_to_ratbag_parsers[] = {
{ MARSGAMING_MM4_ACTION_LEFT_CLICK, marsgaming_button_action_lookup },
{ MARSGAMING_MM4_ACTION_RIGHT_CLICK, marsgaming_button_action_lookup },
{ MARSGAMING_MM4_ACTION_MIDDLE_CLICK, marsgaming_button_action_lookup },
{ MARSGAMING_MM4_ACTION_BACKWARD, marsgaming_button_action_lookup },
{ MARSGAMING_MM4_ACTION_FORWARD, marsgaming_button_action_lookup },
{ MARSGAMING_MM4_ACTION_DPI_SWITCH, marsgaming_button_action_lookup },
{ MARSGAMING_MM4_ACTION_DPI_MINUS, marsgaming_button_action_lookup },
{ MARSGAMING_MM4_ACTION_DPI_PLUS, marsgaming_button_action_lookup },
{ MARSGAMING_MM4_ACTION_PROFILE_SWITCH, marsgaming_button_action_lookup },
{ MARSGAMING_MM4_ACTION_MEDIA, marsgaming_button_action_media },
{ MARSGAMING_MM4_ACTION_COMBO_KEY, marsgaming_button_action_key },
{ MARSGAMING_MM4_ACTION_SINGLE_KEY, marsgaming_button_action_key },
{ MARSGAMING_MM4_ACTION_MACRO, marsgaming_button_action_macro },
{ MARSGAMING_MM4_ACTION_FIRE, marsgaming_button_action_fire },
};
struct ratbag_button_action
marsgaming_parse_button_to_action(struct ratbag_button *button,
const struct marsgaming_button_info *button_info)
{
const struct marsgaming_button_action_to_ratbag_parser *parser;
ARRAY_FOR_EACH(marsgaming_button_action_to_ratbag_parsers, parser) {
if (button_info->action == parser->marsgaming_action_id)
return parser->parse_action(button, button_info);
}
// If no action matches, set it to unknown
return (struct ratbag_button_action)BUTTON_ACTION_UNKNOWN;
}
struct marsgaming_from_ratbag_button_action_to_parser {
enum ratbag_button_action_type ratbag_action_type;
struct marsgaming_button_info (*parse_action)(struct ratbag_button *button);
};
static const struct marsgaming_from_ratbag_button_action_to_parser marsgaming_button_action_to_marsgaming_parsers[] = {
{ RATBAG_BUTTON_ACTION_TYPE_NONE, marsgaming_from_ratbag_to_action_none },
{ RATBAG_BUTTON_ACTION_TYPE_BUTTON, marsgaming_from_ratbag_to_action_button },
{ RATBAG_BUTTON_ACTION_TYPE_SPECIAL, marsgaming_from_ratbag_to_action_special },
{ RATBAG_BUTTON_ACTION_TYPE_MACRO, marsgaming_from_ratbag_to_action_macro },
{ RATBAG_BUTTON_ACTION_TYPE_UNKNOWN, marsgaming_from_ratbag_to_action_none },
};
struct marsgaming_optional_button_info
marsgaming_button_of_type(struct ratbag_button *button)
{
const struct marsgaming_from_ratbag_button_action_to_parser *parser;
ARRAY_FOR_EACH(marsgaming_button_action_to_marsgaming_parsers, parser) {
if (button->action.type != parser->ratbag_action_type)
continue;
return (struct marsgaming_optional_button_info){
.is_present = 1,
.button_info = parser->parse_action(button)
};
}
return (struct marsgaming_optional_button_info){
.is_present = 0,
.none = 1
};
}

View File

@@ -0,0 +1,82 @@
#pragma once
#include "libratbag-private.h"
enum marsgaming_button_action {
MARSGAMING_MM4_ACTION_LEFT_CLICK = 0x01,
MARSGAMING_MM4_ACTION_RIGHT_CLICK = 0x02,
MARSGAMING_MM4_ACTION_MIDDLE_CLICK = 0x03,
MARSGAMING_MM4_ACTION_BACKWARD = 0x04,
MARSGAMING_MM4_ACTION_FORWARD = 0x05,
MARSGAMING_MM4_ACTION_UNKNOWN_6 = 0x06,
MARSGAMING_MM4_ACTION_UNKNOWN_7 = 0x07,
MARSGAMING_MM4_ACTION_DPI_SWITCH = 0x08, // DPI_CYCLE_UP
MARSGAMING_MM4_ACTION_DPI_MINUS = 0x09,
MARSGAMING_MM4_ACTION_DPI_PLUS = 0x0a,
MARSGAMING_MM4_ACTION_UNKNOWN_B = 0x0b,
MARSGAMING_MM4_ACTION_UNKNOWN_C = 0x0c,
MARSGAMING_MM4_ACTION_PROFILE_SWITCH = 0x0d,
MARSGAMING_MM4_ACTION_DISABLE = 0x0e, // Same code as media, but with null additional data. Will handle them the same
MARSGAMING_MM4_ACTION_MEDIA = 0x0e,
MARSGAMING_MM4_ACTION_COMBO_KEY = 0x0f,
MARSGAMING_MM4_ACTION_SINGLE_KEY = 0x10,
MARSGAMING_MM4_ACTION_MACRO = 0x11, // TODO: Implement macros
MARSGAMING_MM4_ACTION_FIRE = 0x12 // Execute left button key X times with specified delay
} __attribute__((packed));
_Static_assert(sizeof(enum marsgaming_button_action) == sizeof(uint8_t), "enum marsgaming_button_action should be 1 byte");
struct marsgaming_button_action_media_data {
uint8_t zero_1;
uint8_t media_key;
uint8_t zero_3;
};
struct marsgaming_button_action_combo_key_data {
uint8_t modifiers;
uint8_t keys[2];
};
struct marsgaming_button_action_single_key_data {
uint8_t zero_0;
uint8_t key;
uint8_t zero_2;
};
struct marsgaming_button_action_macro_data {
uint8_t macro_id;
uint8_t macro_length; // Maybe?
uint8_t unknown_2;
};
struct marsgaming_button_action_fire_data {
uint8_t times;
uint8_t delay_ms;
uint8_t unknown_2;
};
struct marsgaming_button_info {
enum marsgaming_button_action action;
union {
struct marsgaming_button_action_media_data media;
struct marsgaming_button_action_combo_key_data combo_key;
struct marsgaming_button_action_single_key_data single_key;
struct marsgaming_button_action_macro_data macro;
struct marsgaming_button_action_fire_data fire;
} action_info;
};
_Static_assert(sizeof(struct marsgaming_button_info) == 4, "Marsgaming button info is not 4 bytes long");
struct marsgaming_optional_button_info {
unsigned is_present;
union {
uint32_t none;
struct marsgaming_button_info button_info;
};
};
struct ratbag_button_action
marsgaming_parse_button_to_action(struct ratbag_button *button,
const struct marsgaming_button_info *button_info);
struct marsgaming_optional_button_info
marsgaming_button_of_type(struct ratbag_button *button);

View File

@@ -0,0 +1,96 @@
#include "marsgaming-command.h"
#include "marsgaming-leds.h"
#include "marsgaming-protocol.h"
void
marsgaming_command_set_current_profile(struct ratbag_device *device, unsigned int profile)
{
uint8_t writedata[16] = { 0x02, 0x02, 0x43, 0x00, 0x01, 0x00, 0xfa, 0xfa };
writedata[8] = (uint8_t)profile;
ratbag_hidraw_set_feature_report(device, writedata[0], writedata, ARRAY_LENGTH(writedata));
}
void
marsgaming_command_profile_set_polling_interval(struct ratbag_profile *profile, uint8_t polling_interval)
{
uint8_t writedata[16] = { 0x02, 0x02, 0x48 /*lower 3 bits = profile*/, 0x00, 0x01, 0x00, 0xfa, 0xfa };
writedata[2] |= (uint8_t)profile->index;
writedata[8] = polling_interval;
ratbag_hidraw_set_feature_report(profile->device, writedata[0], writedata, ARRAY_LENGTH(writedata));
}
void
marsgaming_command_profile_set_led(struct ratbag_led *led)
{
uint8_t marsgaming_brightness;
uint8_t marsgaming_breathing;
switch (led->mode) {
case RATBAG_LED_OFF:
marsgaming_brightness = 0;
marsgaming_breathing = MARSGAMING_LED_BREATHING_OFF;
break;
case RATBAG_LED_ON:
marsgaming_brightness = led->brightness * 3 / 255;
marsgaming_breathing = MARSGAMING_LED_BREATHING_OFF;
break;
case RATBAG_LED_BREATHING:
case RATBAG_LED_CYCLE: // Not supported by the mouse, let's pretend it's breathing
marsgaming_brightness = led->brightness * 3 / 255;
marsgaming_breathing = led->ms / 2000;
break;
}
if (led->profile->is_active) {
uint8_t writedata[16] = { 0x02, 0x04, 0x00 /*red*/, 0x00 /*green*/, 0x00 /*blue*/, 0x00 /*brightness*/, 0x00 /*breathing*/, 0x01 };
writedata[2] = 0xff - (uint8_t)led->color.red;
writedata[3] = 0xff - (uint8_t)led->color.green;
writedata[4] = 0xff - (uint8_t)led->color.blue;
writedata[5] = marsgaming_brightness; // Brightness level
writedata[6] = marsgaming_breathing; // Breathing mode
ratbag_hidraw_set_feature_report(led->profile->device, writedata[0], writedata, ARRAY_LENGTH(writedata));
}
{
uint8_t writedata[16] = { 0x02, 0x02, 0xf1, /*profile*/ 0x00, 0x06, 0x00 /*led_id???*/, 0xfa, 0xfa };
writedata[3] = (uint8_t)led->profile->index;
writedata[8] = 0xff - (uint8_t)led->color.red;
writedata[9] = 0xff - (uint8_t)led->color.green;
writedata[10] = 0xff - (uint8_t)led->color.blue;
writedata[11] = marsgaming_brightness; // Brightness level
writedata[12] = marsgaming_breathing; // Breathing mode
ratbag_hidraw_set_feature_report(led->profile->device, writedata[0], writedata, ARRAY_LENGTH(writedata));
}
}
void
marsgaming_command_profile_set_resolutions(struct ratbag_profile *profile)
{
struct marsgaming_report_resolutions read_report = marsgaming_profile_get_drv_data(profile)->resolutions_report;
// Copy the read report to adapt it for writing
struct marsgaming_report_resolutions report;
memcpy(&report, &read_report, sizeof(report));
report.report_type = MARSGAMING_MM4_REPORT_TYPE_WRITE;
report.unknown_6 = 0xfa;
report.unknown_7 = 0xfa;
for (int i = 0; i < report.count_resolutions; ++i) {
// Handle endianness
set_unaligned_le_u16((uint8_t*)&report.resolutions[i].x_res, report.resolutions[i].x_res);
set_unaligned_le_u16((uint8_t*)&report.resolutions[i].y_res, report.resolutions[i].y_res);
}
ratbag_hidraw_set_feature_report(profile->device, report.usb_report_id, (uint8_t*)&report, sizeof(report));
}
void
marsgaming_command_profile_set_buttons(struct ratbag_profile *profile)
{
struct marsgaming_report_buttons report = marsgaming_profile_get_drv_data(profile)->buttons_report;
report.report_type = MARSGAMING_MM4_REPORT_TYPE_WRITE;
report.unknown_6 = 0xfa;
report.unknown_7 = 0xfa;
ratbag_hidraw_set_feature_report(profile->device, report.usb_report_id, (uint8_t*)&report, sizeof(report));
}

View File

@@ -0,0 +1,17 @@
#pragma once
#include "libratbag-private.h"
void
marsgaming_command_set_current_profile(struct ratbag_device *device, unsigned int profile);
void
marsgaming_command_profile_set_polling_interval(struct ratbag_profile *profile, uint8_t polling_interval);
void
marsgaming_command_profile_set_led(struct ratbag_led *led);
void
marsgaming_command_profile_set_resolutions(struct ratbag_profile *profile);
void
marsgaming_command_profile_set_buttons(struct ratbag_profile *profile);

View File

@@ -0,0 +1,147 @@
#include "marsgaming-commit.h"
#include "marsgaming-definitions.h"
#include "marsgaming-probe.h"
#include "marsgaming-query.h"
#include "marsgaming-protocol.h"
#include "marsgaming-buttons.h"
#include "marsgaming-command.h"
static int
marsgaming_commit_button(struct ratbag_button *button)
{
struct marsgaming_profile_drv_data *profile_data = marsgaming_profile_get_drv_data(button->profile);
struct marsgaming_button_info *button_info = &profile_data->buttons_report.buttons[button->index];
const struct marsgaming_optional_button_info button_data = marsgaming_button_of_type(button);
if (button_data.is_present)
*button_info = button_data.button_info;
return 0;
}
static int
marsgaming_commit_led(struct ratbag_led *led)
{
if (!led->dirty)
return 0;
marsgaming_command_profile_set_led(led);
return 0;
}
static int
marsgaming_commit_profile_report_rate(struct ratbag_profile *profile)
{
if (!profile->rate_dirty)
return 0;
uint8_t polling_interval = 1000 / ratbag_profile_get_report_rate(profile);
marsgaming_command_profile_set_polling_interval(profile, polling_interval);
return 0;
}
static int
marsgaming_commit_profile_buttons(struct ratbag_profile *profile)
{
bool buttons_dirty = false;
struct ratbag_button *button;
ratbag_profile_for_each_button(profile, button) {
if (button->dirty) {
buttons_dirty = true;
break;
}
}
if (!buttons_dirty)
return 0;
ratbag_profile_for_each_button(profile, button) {
if (!button->dirty)
continue;
marsgaming_commit_button(button);
}
marsgaming_command_profile_set_buttons(profile);
return 0;
}
static int
marsgaming_commit_profile_leds(struct ratbag_profile *profile)
{
struct ratbag_led *led;
ratbag_profile_for_each_led(profile, led) {
marsgaming_commit_led(led);
}
return 0;
}
static int
marsgaming_commit_profile_resolutions(struct ratbag_profile *profile)
{
bool resolutions_dirty = false;
struct ratbag_resolution *resolution;
ratbag_profile_for_each_resolution(profile, resolution) {
if (resolution->dirty) {
resolutions_dirty = true;
break;
}
}
if (!resolutions_dirty)
return 0;
struct marsgaming_profile_drv_data *profile_data = marsgaming_profile_get_drv_data(profile);
ratbag_profile_for_each_resolution(profile, resolution) {
if (!resolution->dirty)
continue;
// Modify the drv_data report so we can send it to the mouse
struct marsgaming_report_resolution_info *resolution_info = &profile_data->resolutions_report.resolutions[resolution->index];
resolution_info->enabled = true;
resolution_info->x_res = resolution->dpi_x / MARSGAMING_MM4_RES_SCALING;
resolution_info->y_res = resolution->dpi_y / MARSGAMING_MM4_RES_SCALING;
resolution_info->led_bitset = ~((~0x0U) << resolution->index);
}
marsgaming_command_profile_set_resolutions(profile);
return 0;
}
static int
marsgaming_commit_profile(struct ratbag_profile *profile)
{
if (!profile->dirty)
return 0;
marsgaming_commit_profile_report_rate(profile);
marsgaming_commit_profile_resolutions(profile);
marsgaming_commit_profile_buttons(profile);
marsgaming_commit_profile_leds(profile);
return 0;
}
static int
marsgaming_commit_profiles(struct ratbag_device *device)
{
uint8_t current_profile = marsgaming_query_current_profile(device);
struct ratbag_profile *profile;
ratbag_device_for_each_profile(device, profile) {
// The user could change the current profile between probe and commit
// We need to modify the active profile for the led changes to take effect
// Unsure how this will interact with internals of ratbag
profile->is_active = current_profile == profile->index;
marsgaming_commit_profile(profile);
}
return 0;
}
int
marsgaming_commit(struct ratbag_device *device)
{
ratbag_open_hidraw(device);
marsgaming_commit_profiles(device);
marsgaming_release_device(device);
return 0;
}
int
marsgaming_set_active_profile(struct ratbag_device *device, unsigned int profile)
{
marsgaming_command_set_current_profile(device, profile);
return 0;
}

View File

@@ -0,0 +1,7 @@
#pragma once
#include "libratbag-private.h"
int
marsgaming_commit(struct ratbag_device *device);
int
marsgaming_set_active_profile(struct ratbag_device *device, unsigned int profile);

View File

@@ -0,0 +1,9 @@
#pragma once
#define MARSGAMING_MM4_NUM_PROFILES 5
#define MARSGAMING_MM4_NUM_RESOLUTIONS_PER_PROFILE 5
#define MARSGAMING_MM4_NUM_BUTTONS 19
#define MARSGAMING_MM4_NUM_LED 1
#define MARSGAMING_MM4_RES_MIN 50
#define MARSGAMING_MM4_RES_MAX 16400
#define MARSGAMING_MM4_RES_SCALING 50

View File

@@ -0,0 +1,11 @@
#include "marsgaming-leds.h"
struct ratbag_color
marsgaming_led_color_to_ratbag(struct marsgaming_led_color color)
{
return (struct ratbag_color){
.red = 0xff - color.red,
.green = 0xff - color.green,
.blue = 0xff - color.blue
};
}

View File

@@ -0,0 +1,27 @@
#pragma once
#include "stdint.h"
#include "libratbag.h"
#define MARSGAMING_LED_BREATHING_OFF 0
/*
* Colors in Mars Gaming are encoded as the inverse of the value.
* That is, a value of 0x00 means fully bright, while 0xFF
* means completely dark.
*/
struct marsgaming_led_color {
uint8_t red;
uint8_t green;
uint8_t blue;
};
struct marsgaming_led {
struct marsgaming_led_color color;
uint8_t brightness;
uint8_t breathing_speed;
} __attribute__((packed));
_Static_assert(sizeof(struct marsgaming_led) == 5, "Marsgaming led is not 5 bytes");
struct ratbag_color
marsgaming_led_color_to_ratbag(struct marsgaming_led_color color);

View File

@@ -0,0 +1,150 @@
#include "marsgaming-probe.h"
#include "marsgaming-definitions.h"
#include "marsgaming-query.h"
static void
marsgaming_probe_button_action(struct ratbag_button *button,
const struct marsgaming_button_info *button_info)
{
struct ratbag_button_action action = marsgaming_parse_button_to_action(button, button_info);
ratbag_button_set_action(button, &action);
}
static void
marsgaming_probe_profile_leds(struct ratbag_profile *profile)
{
struct marsgaming_profile_drv_data *profile_data = marsgaming_profile_get_drv_data(profile);
profile_data->led_report = marsgaming_query_profile_led(profile);
struct ratbag_led *led;
ratbag_profile_for_each_led(profile, led) {
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);
led->colordepth = RATBAG_LED_COLORDEPTH_RGB_888;
struct marsgaming_report_led report = profile_data->led_report;
led->color = marsgaming_led_color_to_ratbag(report.led.color);
led->brightness = report.led.brightness * (255 / 3);
if (report.led.brightness == 0) {
led->mode = RATBAG_LED_OFF;
} else if (report.led.breathing_speed == 0 || report.led.breathing_speed >= 10) {
led->mode = RATBAG_LED_ON;
} else { // Breathing mode
led->mode = RATBAG_LED_BREATHING;
led->ms = report.led.breathing_speed * 2000;
}
}
}
static void
marsgaming_probe_button(struct ratbag_button *button, struct marsgaming_button_info *button_info)
{
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_MACRO);
marsgaming_probe_button_action(button, button_info);
}
static void
marsgaming_probe_profile_buttons(struct ratbag_profile *profile)
{
struct marsgaming_profile_drv_data *profile_data = marsgaming_profile_get_drv_data(profile);
profile_data->buttons_report = marsgaming_query_profile_buttons(profile);
struct ratbag_button *button;
ratbag_profile_for_each_button(profile, button) {
marsgaming_probe_button(button, &profile_data->buttons_report.buttons[button->index]);
}
}
static void
marsgaming_probe_profile_resolutions(struct ratbag_profile *profile)
{
struct marsgaming_profile_drv_data *profile_data = marsgaming_profile_get_drv_data(profile);
profile_data->resolutions_report = marsgaming_query_profile_resolutions(profile);
struct ratbag_resolution *resolution;
ratbag_profile_for_each_resolution(profile, resolution) {
struct marsgaming_report_resolution_info queried_resolution = profile_data->resolutions_report.resolutions[resolution->index];
ratbag_resolution_set_dpi_list_from_range(resolution,
MARSGAMING_MM4_RES_MIN, MARSGAMING_MM4_RES_MAX);
resolution->is_active = profile_data->resolutions_report.current_resolution == resolution->index;
resolution->dpi_x = queried_resolution.x_res * MARSGAMING_MM4_RES_SCALING;
resolution->dpi_y = queried_resolution.y_res * MARSGAMING_MM4_RES_SCALING;
ratbag_resolution_set_cap(resolution, RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION);
}
}
static void
marsgaming_probe_profile_report_rate(struct ratbag_profile *profile)
{
static const unsigned int rates[] = { 125, 250, 500, 1000 };
ratbag_profile_set_report_rate_list(profile, rates, ARRAY_LENGTH(rates));
const uint8_t interval = marsgaming_query_profile_polling_interval(profile);
profile->hz = 1000 / interval;
}
static void
marsgaming_probe_profiles(struct ratbag_device *device)
{
uint8_t current_profile = marsgaming_query_current_profile(device);
struct ratbag_profile *profile;
ratbag_device_for_each_profile(device, profile) {
profile->drv_data = zalloc(sizeof(struct marsgaming_profile_drv_data));
profile->is_active = (profile->index == current_profile);
marsgaming_probe_profile_report_rate(profile);
marsgaming_probe_profile_resolutions(profile);
marsgaming_probe_profile_buttons(profile);
marsgaming_probe_profile_leds(profile);
}
}
static void
marsgaming_initialize_device(struct ratbag_device *device)
{
ratbag_device_init_profiles(device,
MARSGAMING_MM4_NUM_PROFILES,
MARSGAMING_MM4_NUM_RESOLUTIONS_PER_PROFILE,
MARSGAMING_MM4_NUM_BUTTONS,
MARSGAMING_MM4_NUM_LED);
}
static int
marsgaming_sanity_check(struct ratbag_device *device)
{
int rc = ratbag_open_hidraw(device);
if (rc)
return rc;
const uint8_t available_reports[] = { 0x02, 0x03, 0x04 };
for (size_t report_id = 0; report_id < ARRAY_LENGTH(available_reports); ++report_id) {
if (!ratbag_hidraw_has_report(device, available_reports[report_id])) {
ratbag_close_hidraw(device);
return -ENODEV;
}
}
return 0;
}
int
marsgaming_probe(struct ratbag_device *device)
{
int rc = marsgaming_sanity_check(device);
if (rc < 0)
return rc;
marsgaming_initialize_device(device);
marsgaming_probe_profiles(device);
marsgaming_release_device(device);
return 0;
}
void
marsgaming_release_device(struct ratbag_device *device)
{
ratbag_close_hidraw(device);
}

View File

@@ -0,0 +1,8 @@
#pragma once
#include "libratbag-private.h"
int
marsgaming_probe(struct ratbag_device *device);
void
marsgaming_release_device(struct ratbag_device *device);

View File

@@ -0,0 +1,87 @@
#pragma once
#include "marsgaming-buttons.h"
#include "marsgaming-leds.h"
enum marsgaming_report_type {
MARSGAMING_MM4_REPORT_TYPE_UNKNOWN_1 = 0x01,
MARSGAMING_MM4_REPORT_TYPE_WRITE = 0x02,
MARSGAMING_MM4_REPORT_TYPE_READ = 0x03,
MARSGAMING_MM4_REPORT_TYPE_UNKNOWN_4 = 0x04,
MARSGAMING_MM4_REPORT_TYPE_UNKNOWN_6 = 0x06
} __attribute__((packed));
_Static_assert(sizeof(enum marsgaming_report_type) == sizeof(uint8_t), "enum marsgaming_report_type should be 1 byte");
struct marsgaming_report_resolution_info {
bool enabled;
uint16_t x_res;
uint16_t y_res;
// 4 lowest bits, each one corresponds to one led
// Resolution 0 -> b0000
// Resolution 1 -> b0001
// Resolution 2 -> b0011
// etc
uint8_t led_bitset;
uint8_t zeros_3[2];
} __attribute__((packed));
struct marsgaming_report_resolutions {
uint8_t usb_report_id;
enum marsgaming_report_type report_type;
uint8_t unknown_2; // 0x4f
uint8_t profile_id;
uint8_t unknown_4; // 0x2a
uint8_t unknown_5; // 0x00
uint8_t unknown_6; // 0x00 from device | 0xfa from host
uint8_t unknown_7; // 0x00 from device | 0xfa from host
uint8_t count_resolutions;
uint8_t current_resolution;
struct marsgaming_report_resolution_info resolutions[6];
uint8_t padding[6];
} __attribute__((packed));
_Static_assert(sizeof(struct marsgaming_report_resolutions) == 64, "Marsgaming resolution report is not 64 bytes long");
struct marsgaming_report_buttons {
uint8_t usb_report_id;
enum marsgaming_report_type report_type;
uint8_t unknown_2; // 0x90
uint8_t profile_id;
uint8_t unknown_4; // 0x4d
uint8_t unknown_5; // 0x00
uint8_t unknown_6; // 0x00 from device | 0xfa from host
uint8_t unknown_7; // 0x00 from device | 0xfa from host
uint8_t button_count;
struct marsgaming_button_info buttons[253];
uint8_t padding[3];
} __attribute__((packed));
_Static_assert(sizeof(struct marsgaming_report_buttons) == 1024, "Marsgaming button report is not 1024 bytes long");
struct marsgaming_report_led {
uint8_t usb_report_id;
enum marsgaming_report_type report_type;
uint8_t unknown2; // 0xf1
uint8_t profile_id;
uint8_t unknown4; // 0x06
uint8_t unknown5; // 0x00
uint8_t unknown6; // 0xfa
uint8_t unknown7; // 0xfa
struct marsgaming_led led;
uint8_t unknown13; // 0x00
uint8_t unknown14; // 0x00
uint8_t unknown15; // 0x00
};
_Static_assert(sizeof(struct marsgaming_report_led) == 16, "Marsgaming led report is not 16 bytes long");
struct marsgaming_profile_drv_data {
struct marsgaming_report_buttons buttons_report;
struct marsgaming_report_resolutions resolutions_report;
struct marsgaming_report_led led_report;
};
inline static struct marsgaming_profile_drv_data *
marsgaming_profile_get_drv_data(struct ratbag_profile *profile)
{
return (struct marsgaming_profile_drv_data *)profile->drv_data;
}

View File

@@ -0,0 +1,68 @@
#include "marsgaming-query.h"
uint8_t
marsgaming_query_current_profile(struct ratbag_device *device)
{
uint8_t writedata[16] = { 0x02, 0x03, 0x43, 0x00, 0x01, 0x00, 0xfa, 0xfa };
ratbag_hidraw_set_feature_report(device, writedata[0], writedata, ARRAY_LENGTH(writedata));
uint8_t readdata[16] = { 0 };
ratbag_hidraw_get_feature_report(device, 0x02, readdata, ARRAY_LENGTH(readdata));
return readdata[8];
}
struct marsgaming_report_resolutions
marsgaming_query_profile_resolutions(struct ratbag_profile *profile)
{
uint8_t writedata[16] = { 0x02, 0x03, 0x4f, (uint8_t)profile->index, 0x2a, 0x00, 0xfa, 0xfa };
ratbag_hidraw_set_feature_report(profile->device, writedata[0], writedata, ARRAY_LENGTH(writedata));
struct marsgaming_report_resolutions report = { 0 };
ratbag_hidraw_get_feature_report(profile->device, 0x03, (uint8_t*)&report, sizeof(report));
// We need to match endianness of the uint16_t values
for (int i = 0; i < report.count_resolutions; ++i) {
report.resolutions[i].x_res = get_unaligned_le_u16((uint8_t*)&report.resolutions[i].x_res);
report.resolutions[i].y_res = get_unaligned_le_u16((uint8_t*)&report.resolutions[i].y_res);
}
return report;
}
struct marsgaming_report_buttons
marsgaming_query_profile_buttons(struct ratbag_profile *profile)
{
uint8_t writedata[16] = { 0x02, 0x03, 0x90, (uint8_t)profile->index, 0x4d, 0x00, 0xfa, 0xfa };
ratbag_hidraw_set_feature_report(profile->device, writedata[0], writedata, ARRAY_LENGTH(writedata));
struct marsgaming_report_buttons report = { 0 };
ratbag_hidraw_get_feature_report(profile->device, 0x04, (uint8_t*)&report, sizeof(report));
return report;
}
uint8_t
marsgaming_query_profile_polling_interval(struct ratbag_profile *profile)
{
// Lower 3 bits of 3rd byte is the profile number
uint8_t writedata[16] = { 0x02, 0x03, 0x48 | (uint8_t)profile->index, 0x00, 0x01, 0x00, 0xfa, 0xfa };
ratbag_hidraw_set_feature_report(profile->device, writedata[0], writedata, ARRAY_LENGTH(writedata));
uint8_t readdata[16] = { 0 };
ratbag_hidraw_get_feature_report(profile->device, 0x02, readdata, ARRAY_LENGTH(readdata));
return readdata[8];
}
struct marsgaming_report_led
marsgaming_query_profile_led(struct ratbag_profile *profile)
{
uint8_t writedata[16] = { 0x02, 0x03, 0xf1, (uint8_t)profile->index, 0x06, 0x00 /*led_id???*/, 0xfa, 0xfa };
ratbag_hidraw_set_feature_report(profile->device, writedata[0], writedata, ARRAY_LENGTH(writedata));
struct marsgaming_report_led report = { 0 };
ratbag_hidraw_get_feature_report(profile->device, 0x04, (uint8_t*)&report, sizeof(report));
return report;
}

View File

@@ -0,0 +1,18 @@
#pragma once
#include "marsgaming-protocol.h"
#include "libratbag-private.h"
uint8_t
marsgaming_query_current_profile(struct ratbag_device *device);
struct marsgaming_report_resolutions
marsgaming_query_profile_resolutions(struct ratbag_profile *profile);
struct marsgaming_report_buttons
marsgaming_query_profile_buttons(struct ratbag_profile *profile);
uint8_t
marsgaming_query_profile_polling_interval(struct ratbag_profile *profile);
struct marsgaming_report_led
marsgaming_query_profile_led(struct ratbag_profile *profile);

View File

@@ -62,6 +62,7 @@ enum driver {
SINOWEALTH,
SINOWEALTH_NUBWO,
OPENINPUT,
MARSGAMING,
};
struct data_hidpp20 {
@@ -453,6 +454,7 @@ static const struct driver_map {
{ SINOWEALTH, "sinowealth", init_data_sinowealth },
{ SINOWEALTH_NUBWO, "sinowealth_nubwo", NULL},
{ OPENINPUT, "openinput", NULL },
{ MARSGAMING, "marsgaming", NULL },
};
const char *

View File

@@ -299,6 +299,8 @@ struct ratbag_profile {
#define BUTTON_ACTION_NONE \
{ .type = RATBAG_BUTTON_ACTION_TYPE_NONE }
#define BUTTON_ACTION_UNKNOWN \
{ .type = RATBAG_BUTTON_ACTION_TYPE_UNKNOWN }
#define BUTTON_ACTION_BUTTON(num_) \
{ .type = RATBAG_BUTTON_ACTION_TYPE_BUTTON, \
.action.button = num_ }
@@ -589,6 +591,7 @@ extern struct ratbag_driver hidpp20_driver;
extern struct ratbag_driver hidpp10_driver;
extern struct ratbag_driver logitech_g300_driver;
extern struct ratbag_driver logitech_g600_driver;
extern struct ratbag_driver marsgaming_driver;
extern struct ratbag_driver roccat_driver;
extern struct ratbag_driver roccat_kone_pure_driver;
extern struct ratbag_driver roccat_emp_driver;

View File

@@ -571,6 +571,7 @@ ratbag_create_context(const struct ratbag_interface *interface,
ratbag_register_driver(ratbag, &hidpp10_driver);
ratbag_register_driver(ratbag, &logitech_g300_driver);
ratbag_register_driver(ratbag, &logitech_g600_driver);
ratbag_register_driver(ratbag, &marsgaming_driver);
ratbag_register_driver(ratbag, &roccat_driver);
ratbag_register_driver(ratbag, &roccat_kone_pure_driver);
ratbag_register_driver(ratbag, &roccat_emp_driver);