forked from drath/libratbag
marsgaming: implement Mars Gaming MM4 protocol
This commit is contained in:
committed by
Yaroslav Chvanov
parent
70f2e96a66
commit
7fb3c63e8d
5
data/devices/marsgaming-mm4.device
Normal file
5
data/devices/marsgaming-mm4.device
Normal file
@@ -0,0 +1,5 @@
|
||||
[Device]
|
||||
Name=Mars Gaming MM4
|
||||
DeviceMatch=usb:04d9:fa58
|
||||
DeviceType=mouse
|
||||
Driver=marsgaming
|
||||
11
meson.build
11
meson.build
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
21
src/driver-marsgaming/driver-marsgaming.c
Normal file
21
src/driver-marsgaming/driver-marsgaming.c
Normal 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
|
||||
};
|
||||
391
src/driver-marsgaming/marsgaming-buttons.c
Normal file
391
src/driver-marsgaming/marsgaming-buttons.c
Normal 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
|
||||
};
|
||||
}
|
||||
82
src/driver-marsgaming/marsgaming-buttons.h
Normal file
82
src/driver-marsgaming/marsgaming-buttons.h
Normal 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);
|
||||
96
src/driver-marsgaming/marsgaming-command.c
Normal file
96
src/driver-marsgaming/marsgaming-command.c
Normal 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));
|
||||
}
|
||||
17
src/driver-marsgaming/marsgaming-command.h
Normal file
17
src/driver-marsgaming/marsgaming-command.h
Normal 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);
|
||||
147
src/driver-marsgaming/marsgaming-commit.c
Normal file
147
src/driver-marsgaming/marsgaming-commit.c
Normal 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;
|
||||
}
|
||||
7
src/driver-marsgaming/marsgaming-commit.h
Normal file
7
src/driver-marsgaming/marsgaming-commit.h
Normal 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);
|
||||
9
src/driver-marsgaming/marsgaming-definitions.h
Normal file
9
src/driver-marsgaming/marsgaming-definitions.h
Normal 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
|
||||
11
src/driver-marsgaming/marsgaming-leds.c
Normal file
11
src/driver-marsgaming/marsgaming-leds.c
Normal 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
|
||||
};
|
||||
}
|
||||
27
src/driver-marsgaming/marsgaming-leds.h
Normal file
27
src/driver-marsgaming/marsgaming-leds.h
Normal 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);
|
||||
150
src/driver-marsgaming/marsgaming-probe.c
Normal file
150
src/driver-marsgaming/marsgaming-probe.c
Normal 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);
|
||||
}
|
||||
8
src/driver-marsgaming/marsgaming-probe.h
Normal file
8
src/driver-marsgaming/marsgaming-probe.h
Normal 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);
|
||||
87
src/driver-marsgaming/marsgaming-protocol.h
Normal file
87
src/driver-marsgaming/marsgaming-protocol.h
Normal 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;
|
||||
}
|
||||
68
src/driver-marsgaming/marsgaming-query.c
Normal file
68
src/driver-marsgaming/marsgaming-query.c
Normal 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;
|
||||
}
|
||||
18
src/driver-marsgaming/marsgaming-query.h
Normal file
18
src/driver-marsgaming/marsgaming-query.h
Normal 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);
|
||||
@@ -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 *
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user