diff --git a/src/driver-hidpp20.c b/src/driver-hidpp20.c index 2ba2d2b1..c4ebcf6f 100644 --- a/src/driver-hidpp20.c +++ b/src/driver-hidpp20.c @@ -97,6 +97,78 @@ hidpp20drv_read_button_1b04(struct ratbag_button *button) ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_SPECIAL); } +static unsigned int +hidpp20drv_read_macro_key_8100(struct ratbag_device *device, union hidpp20_macro_data *macro) +{ + switch (macro->key.modifier) { + case 0x01: return KEY_LEFTCTRL; + case 0x02: return KEY_LEFTSHIFT; + case 0x04: return KEY_LEFTALT; + case 0x08: return KEY_LEFTMETA; + case 0x10: return KEY_RIGHTCTRL; + case 0x20: return KEY_RIGHTSHIFT; + case 0x40: return KEY_RIGHTALT; + case 0x80: return KEY_RIGHTMETA; + } + + return ratbag_hidraw_get_keycode_from_keyboard_usage(device, macro->key.key); +} + +static void +hidpp20drv_read_macro_8100(struct ratbag_button *button, + struct hidpp20_profile *profile, + union hidpp20_button_binding *binding) +{ + struct ratbag_device *device = button->profile->device; + union hidpp20_macro_data *macro; + unsigned int i, keycode; + bool delay = true; + + macro = profile->macros[binding->macro.page]; + + ratbag_button_set_macro(button, "macro"); + i = 0; + + while (macro && macro->any.type != HIDPP20_MACRO_END && i < MAX_MACRO_EVENTS) { + switch (macro->any.type) { + case HIDPP20_MACRO_DELAY: + ratbag_button_set_macro_event(button, + i++, + RATBAG_MACRO_EVENT_WAIT, + macro->delay.time); + delay = true; + break; + case HIDPP20_MACRO_KEY_PRESS: + keycode = hidpp20drv_read_macro_key_8100(device, macro); + if (!delay) + ratbag_button_set_macro_event(button, + i++, + RATBAG_MACRO_EVENT_WAIT, + 1); + ratbag_button_set_macro_event(button, + i++, + RATBAG_MACRO_EVENT_KEY_PRESSED, + keycode); + delay = false; + break; + case HIDPP20_MACRO_KEY_RELEASE: + keycode = hidpp20drv_read_macro_key_8100(device, macro); + if (!delay) + ratbag_button_set_macro_event(button, + i++, + RATBAG_MACRO_EVENT_WAIT, + 1); + ratbag_button_set_macro_event(button, + i++, + RATBAG_MACRO_EVENT_KEY_RELEASED, + keycode); + delay = false; + break; + } + macro++; + } +} + static void hidpp20drv_read_button_8100(struct ratbag_button *button) { @@ -132,6 +204,9 @@ hidpp20drv_read_button_8100(struct ratbag_button *button) button->action.type = RATBAG_BUTTON_ACTION_TYPE_SPECIAL; button->action.action.special = hidpp20_onboard_profiles_get_special(profile->buttons[button->index].special.special); break; + case HIDPP20_BUTTON_MACRO: + hidpp20drv_read_macro_8100(button, profile, &profile->buttons[button->index]); + break; default: button->action.type = RATBAG_BUTTON_ACTION_TYPE_UNKNOWN; } diff --git a/src/hidpp20.c b/src/hidpp20.c index a16a2ab3..7e25920d 100644 --- a/src/hidpp20.c +++ b/src/hidpp20.c @@ -1133,6 +1133,166 @@ hidpp20_onboard_profiles_initialize(struct hidpp20_device *device, return 0; } +static int +hidpp20_onboard_profiles_macro_next(struct hidpp20_device *device, + uint8_t memory[32], + uint16_t *index, + union hidpp20_macro_data *macro) +{ + int rc = 0; + unsigned int step = 1; + + if (*index >= 32 - sizeof(union hidpp20_macro_data)) { + hidpp_log_error(&device->base, "error while parsing macro.\n"); + return -EFAULT; + } + + memcpy(macro, &memory[*index], sizeof(union hidpp20_macro_data)); + + switch (macro->any.type) { + case HIDPP20_MACRO_DELAY: + /* fallthrough */ + case HIDPP20_MACRO_KEY_PRESS: + /* fallthrough */ + case HIDPP20_MACRO_KEY_RELEASE: + /* fallthrough */ + case HIDPP20_MACRO_JUMP: + step = 3; + rc = -EAGAIN; + break; + case HIDPP20_MACRO_NOOP: + step = 1; + rc = -EAGAIN; + break; + case HIDPP20_MACRO_END: + step = 1; + return 0; + default: + hidpp_log_error(&device->base, "unknown tag: 0x%02x\n", macro->any.type); + rc = -EFAULT; + } + + if ((*index + step) & 0xF0) + /* the next item will be on the following chunk */ + return -ENOMEM; + + *index += step; + + return rc; +} + +static int +hidpp20_onboard_profiles_read_macro(struct hidpp20_device *device, + uint8_t page, uint8_t offset, + union hidpp20_macro_data **return_macro) +{ + uint8_t memory[HIDPP20_PAGE_SIZE]; + union hidpp20_macro_data *macro = NULL; + unsigned count = 0; + unsigned index = 0; + uint16_t mem_index = 0; + int rc = -ENOMEM; + + do { + if (count == index) { + union hidpp20_macro_data *tmp; + + count += 32; + /* manual realloc to have the memory zero-initialized */ + tmp = zalloc(count * sizeof(union hidpp20_macro_data)); + if (macro) { + memcpy(tmp, macro, (count - 32) * sizeof(union hidpp20_macro_data)); + free(macro); + } + macro = tmp; + } + + if (rc == -ENOMEM) { + offset += mem_index; + rc = hidpp20_onboard_profiles_read_memory(device, + 0, + page, + offset, + memory); + if (rc) + goto out_err; + + mem_index = 0; + } + + rc = hidpp20_onboard_profiles_macro_next(device, + memory, + &mem_index, + ¯o[index]); + if (rc == -EFAULT) + goto out_err; + + if (rc != -ENOMEM) { + if (macro[index].any.type == HIDPP20_MACRO_JUMP) { + page = macro[index].jump.page; + offset = macro[index].jump.offset; + mem_index = 0; + /* no need to store the jump in memory */ + index--; + /* force memory fetching */ + rc = -ENOMEM; + } + + index++; + } + } while (rc); + + *return_macro = macro; + return index; + +out_err: + if (macro) + free(macro); + + return rc; +} + +static int +hidpp20_onboard_profiles_parse_macro(struct hidpp20_device *device, + uint8_t page, uint8_t offset, + union hidpp20_macro_data **return_macro) +{ + union hidpp20_macro_data *m, *macro = NULL; + unsigned i, count = 0; + int rc; + + rc = hidpp20_onboard_profiles_read_macro(device, page, offset, ¯o); + if (rc < 0) + return rc; + + count = rc; + + for (i = 0; i < count; i++) { + m = ¯o[i]; + switch (m->any.type) { + case HIDPP20_MACRO_DELAY: + m->delay.time = hidpp_be_u16_to_cpu(m->delay.time); + break; + case HIDPP20_MACRO_KEY_PRESS: + break; + case HIDPP20_MACRO_KEY_RELEASE: + break; + case HIDPP20_MACRO_JUMP: + break; + case HIDPP20_MACRO_END: + break; + case HIDPP20_MACRO_NOOP: + break; + default: + hidpp_log_error(&device->base, "unknown tag: 0x%02x\n", m->any.type); + } + } + + *return_macro = macro; + + return 0; +} + int hidpp20_onboard_profiles_allocate(struct hidpp20_device *device, struct hidpp20_profiles **profiles_list) @@ -1177,9 +1337,22 @@ void hidpp20_onboard_profiles_destroy(struct hidpp20_device *device, struct hidpp20_profiles *profiles_list) { + struct hidpp20_profile *profile; + union hidpp20_macro_data **macro; + unsigned i; + if (!profiles_list) return; + for (i = 0; i < profiles_list->num_profiles; i++) { + profile = &profiles_list->profiles[i]; + + ARRAY_FOR_EACH(profile->macros, macro) { + if (*macro) + free(*macro); + } + } + free(profiles_list); } @@ -1288,7 +1461,8 @@ union hidpp20_internal_profile { _Static_assert(sizeof(union hidpp20_internal_profile) == HIDPP20_PROFILE_SIZE, "Invalid size"); static void -hidpp20_buttons_to_cpu(struct hidpp20_profile *profile, +hidpp20_buttons_to_cpu(struct hidpp20_device *device, + struct hidpp20_profile *profile, union hidpp20_button_binding *buttons, unsigned int count) { @@ -1320,6 +1494,17 @@ hidpp20_buttons_to_cpu(struct hidpp20_profile *profile, case HIDPP20_BUTTON_SPECIAL: button->special.special = b->special.special; break; + case HIDPP20_BUTTON_MACRO: + if (profile->macros[i]) { + free(profile->macros[i]); + profile->macros[i] = NULL; + } + hidpp20_onboard_profiles_parse_macro(device, + b->macro.page, + b->macro.offset, + &profile->macros[i]); + button->macro.page = i; + break; case HIDPP20_BUTTON_DISABLED: break; default: @@ -1396,7 +1581,7 @@ int hidpp20_onboard_profiles_read(struct hidpp20_device *device, profile->dpi[i] = hidpp_get_unaligned_le_u16(&data[2 * i + 3]); } - hidpp20_buttons_to_cpu(profile, pdata.profile.buttons, profiles_list->num_buttons); + hidpp20_buttons_to_cpu(device, profile, pdata.profile.buttons, profiles_list->num_buttons); memcpy(profile->name, pdata.profile.name.txt, sizeof(profile->name)); /* force terminating '\0' */ diff --git a/src/hidpp20.h b/src/hidpp20.h index a77a3902..04a6a7e7 100644 --- a/src/hidpp20.h +++ b/src/hidpp20.h @@ -269,6 +269,7 @@ int hidpp20_adjustable_dpi_set_sensor_dpi(struct hidpp20_device *device, #define HIDPP20_BUTTON_HID_TYPE_KEYBOARD 0x02 #define HIDPP20_BUTTON_HID_TYPE_CONSUMER_CONTROL 0x03 #define HIDPP20_BUTTON_SPECIAL 0x90 +#define HIDPP20_BUTTON_MACRO 0x00 #define HIDPP20_BUTTON_DISABLED 0xFF #define HIDPP20_MODIFIER_KEY_CTRL 0x01 @@ -302,12 +303,49 @@ union hidpp20_button_binding { uint8_t type; /* HIDPP20_BUTTON_SPECIAL */ uint8_t special; } __attribute__((packed)) special; + struct { + uint8_t type; /* HIDPP20_BUTTON_MACRO */ + uint8_t page; + uint8_t zero; + uint8_t offset; + } __attribute__((packed)) macro; struct { uint8_t type; /* PROFILE_BUTTON_TYPE_DISABLED */ } disabled; } __attribute__((packed)); _Static_assert(sizeof(union hidpp20_button_binding) == 4, "Invalid size"); +#define HIDPP20_MACRO_NOOP 0x01 +#define HIDPP20_MACRO_DELAY 0x40 +#define HIDPP20_MACRO_KEY_PRESS 0x43 +#define HIDPP20_MACRO_KEY_RELEASE 0x44 +#define HIDPP20_MACRO_JUMP 0x60 +#define HIDPP20_MACRO_END 0xff + +union hidpp20_macro_data { + struct { + uint8_t type; + } __attribute__((packed)) any; + struct { + uint8_t type; /* HIDPP20_MACRO_DELAY */ + uint16_t time; + } __attribute__((packed)) delay; + struct { + uint8_t type; /* HIDPP20_MACRO_KEY_PRESS or HIDPP20_MACRO_KEY_RELEASE */ + uint8_t modifier; + uint8_t key; + } __attribute__((packed)) key; + struct { + uint8_t type; /* HIDPP20_MACRO_JUMP */ + uint8_t offset; + uint8_t page; + } __attribute__((packed)) jump; + struct { + uint8_t type; /* HIDPP20_MACRO_END */ + } __attribute__((packed)) end; +} __attribute__((packed)); +_Static_assert(sizeof(union hidpp20_macro_data) == 3, "Invalid size"); + struct hidpp20_profile { uint8_t index; uint8_t enabled; @@ -317,6 +355,7 @@ struct hidpp20_profile { unsigned switched_dpi; uint16_t dpi[5]; union hidpp20_button_binding buttons[32]; + union hidpp20_macro_data *macros[32]; }; struct hidpp20_profiles {