diff --git a/data/devices/marsgaming-mm4.device b/data/devices/marsgaming-mm4.device new file mode 100644 index 00000000..78f10486 --- /dev/null +++ b/data/devices/marsgaming-mm4.device @@ -0,0 +1,5 @@ +[Device] +Name=Mars Gaming MM4 +DeviceMatch=usb:04d9:fa58 +DeviceType=mouse +Driver=marsgaming diff --git a/meson.build b/meson.build index 91b4daba..3a74aee1 100644 --- a/meson.build +++ b/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, ) diff --git a/src/driver-marsgaming/driver-marsgaming.c b/src/driver-marsgaming/driver-marsgaming.c new file mode 100644 index 00000000..78e02c20 --- /dev/null +++ b/src/driver-marsgaming/driver-marsgaming.c @@ -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 +}; diff --git a/src/driver-marsgaming/marsgaming-buttons.c b/src/driver-marsgaming/marsgaming-buttons.c new file mode 100644 index 00000000..9ac37aa3 --- /dev/null +++ b/src/driver-marsgaming/marsgaming-buttons.c @@ -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 + }; +} diff --git a/src/driver-marsgaming/marsgaming-buttons.h b/src/driver-marsgaming/marsgaming-buttons.h new file mode 100644 index 00000000..9a57e935 --- /dev/null +++ b/src/driver-marsgaming/marsgaming-buttons.h @@ -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); diff --git a/src/driver-marsgaming/marsgaming-command.c b/src/driver-marsgaming/marsgaming-command.c new file mode 100644 index 00000000..7b33e108 --- /dev/null +++ b/src/driver-marsgaming/marsgaming-command.c @@ -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)); +} diff --git a/src/driver-marsgaming/marsgaming-command.h b/src/driver-marsgaming/marsgaming-command.h new file mode 100644 index 00000000..a067fcf6 --- /dev/null +++ b/src/driver-marsgaming/marsgaming-command.h @@ -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); diff --git a/src/driver-marsgaming/marsgaming-commit.c b/src/driver-marsgaming/marsgaming-commit.c new file mode 100644 index 00000000..9a684825 --- /dev/null +++ b/src/driver-marsgaming/marsgaming-commit.c @@ -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; +} diff --git a/src/driver-marsgaming/marsgaming-commit.h b/src/driver-marsgaming/marsgaming-commit.h new file mode 100644 index 00000000..9cffbc53 --- /dev/null +++ b/src/driver-marsgaming/marsgaming-commit.h @@ -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); diff --git a/src/driver-marsgaming/marsgaming-definitions.h b/src/driver-marsgaming/marsgaming-definitions.h new file mode 100644 index 00000000..f6cf3898 --- /dev/null +++ b/src/driver-marsgaming/marsgaming-definitions.h @@ -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 diff --git a/src/driver-marsgaming/marsgaming-leds.c b/src/driver-marsgaming/marsgaming-leds.c new file mode 100644 index 00000000..e37c1a4c --- /dev/null +++ b/src/driver-marsgaming/marsgaming-leds.c @@ -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 + }; +} diff --git a/src/driver-marsgaming/marsgaming-leds.h b/src/driver-marsgaming/marsgaming-leds.h new file mode 100644 index 00000000..525200e7 --- /dev/null +++ b/src/driver-marsgaming/marsgaming-leds.h @@ -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); diff --git a/src/driver-marsgaming/marsgaming-probe.c b/src/driver-marsgaming/marsgaming-probe.c new file mode 100644 index 00000000..b4c0e7b9 --- /dev/null +++ b/src/driver-marsgaming/marsgaming-probe.c @@ -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); +} diff --git a/src/driver-marsgaming/marsgaming-probe.h b/src/driver-marsgaming/marsgaming-probe.h new file mode 100644 index 00000000..3985f92e --- /dev/null +++ b/src/driver-marsgaming/marsgaming-probe.h @@ -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); diff --git a/src/driver-marsgaming/marsgaming-protocol.h b/src/driver-marsgaming/marsgaming-protocol.h new file mode 100644 index 00000000..36df13db --- /dev/null +++ b/src/driver-marsgaming/marsgaming-protocol.h @@ -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; +} diff --git a/src/driver-marsgaming/marsgaming-query.c b/src/driver-marsgaming/marsgaming-query.c new file mode 100644 index 00000000..77747557 --- /dev/null +++ b/src/driver-marsgaming/marsgaming-query.c @@ -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; +} diff --git a/src/driver-marsgaming/marsgaming-query.h b/src/driver-marsgaming/marsgaming-query.h new file mode 100644 index 00000000..c6ec2702 --- /dev/null +++ b/src/driver-marsgaming/marsgaming-query.h @@ -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); diff --git a/src/libratbag-data.c b/src/libratbag-data.c index 7a302eba..1fd037ad 100644 --- a/src/libratbag-data.c +++ b/src/libratbag-data.c @@ -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 * diff --git a/src/libratbag-private.h b/src/libratbag-private.h index 8fda6591..3715c36f 100644 --- a/src/libratbag-private.h +++ b/src/libratbag-private.h @@ -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; diff --git a/src/libratbag.c b/src/libratbag.c index 18ccadd9..32b3a6d8 100644 --- a/src/libratbag.c +++ b/src/libratbag.c @@ -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);