From 06d0c03ea1b6f9dbd037f99afbdba9c5ae670d45 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Thu, 17 Dec 2015 13:19:19 +0100 Subject: [PATCH] hidpp10: parse macros Macros are somewhat properly parsed in hidpp10. The driver, however, supports only key press/release and delays. Signed-off-by: Benjamin Tissoires --- src/driver-hidpp10.c | 103 ++++++++++++++- src/hidpp10.c | 295 ++++++++++++++++++++++++++++++++++++++++++- src/hidpp10.h | 73 +++++++++++ 3 files changed, 466 insertions(+), 5 deletions(-) diff --git a/src/driver-hidpp10.c b/src/driver-hidpp10.c index 67be65c1..f25c2518 100644 --- a/src/driver-hidpp10.c +++ b/src/driver-hidpp10.c @@ -53,6 +53,106 @@ struct hidpp10drv_data { struct hidpp10_device *dev; }; +static unsigned int +hidpp10drv_read_macro_modifier(struct ratbag_device *device, union hidpp10_macro_data *macro) +{ + switch (macro->key.key) { + 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 KEY_RESERVED; +} + +static void +hidpp10drv_read_macro(struct ratbag_button *button, + struct hidpp10_profile *profile, + union hidpp10_button *binding) +{ + struct ratbag_device *device = button->profile->device; + const char *name; + union hidpp10_macro_data *macro; + unsigned int i, keycode; + bool delay = true; + + macro = profile->macros[binding->macro.address]; + + name = binding->macro.address > 1 ? (const char *)profile->macro_names[binding->macro.address - 2] : ""; + ratbag_button_set_macro(button, name); + i = 0; + + while (macro && macro->any.type != HIDPP10_MACRO_END && i < MAX_MACRO_EVENTS) { + switch (macro->any.type) { + case HIDPP10_MACRO_DELAY: + ratbag_button_set_macro_event(button, + i++, + RATBAG_MACRO_EVENT_WAIT, + macro->delay.time); + delay = true; + break; + case HIDPP10_MACRO_KEY_PRESS: + keycode = ratbag_hidraw_get_keycode_from_keyboard_usage(device, macro->key.key); + 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 HIDPP10_MACRO_KEY_RELEASE: + keycode = ratbag_hidraw_get_keycode_from_keyboard_usage(device, macro->key.key); + 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; + case HIDPP10_MACRO_MOD_PRESS: + keycode = hidpp10drv_read_macro_modifier(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 HIDPP10_MACRO_MOD_RELEASE: + keycode = hidpp10drv_read_macro_modifier(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 hidpp10drv_map_button(struct ratbag_device *device, struct hidpp10_device *hidpp10, @@ -91,8 +191,7 @@ hidpp10drv_map_button(struct ratbag_device *device, if (profile.buttons[button->index].any.type & 0x80) { button->action.type = RATBAG_BUTTON_ACTION_TYPE_UNKNOWN; } else { - /* FIXME: MACRO */ - button->action.type = RATBAG_BUTTON_ACTION_TYPE_NONE; + hidpp10drv_read_macro(button, &profile, &profile.buttons[button->index]); } } diff --git a/src/hidpp10.c b/src/hidpp10.c index 6f91e25c..86969250 100644 --- a/src/hidpp10.c +++ b/src/hidpp10.c @@ -568,6 +568,11 @@ union _hidpp10_button_binding { uint8_t zero0; uint8_t zero1; } disabled; + struct { + uint8_t page; + uint8_t offset; + uint8_t zero; + } __attribute__((packed)) macro; } __attribute__((packed)); _Static_assert(sizeof(union _hidpp10_button_binding) == 3, "Invalid size"); @@ -920,6 +925,266 @@ hidpp10_write_dpi_modes_16(struct hidpp10_device *dev, } } +static int +hidpp10_onboard_profiles_macro_next(struct hidpp10_device *device, + uint8_t memory[32], + uint16_t *index, + union hidpp10_macro_data *macro) +{ + int rc = 0; + unsigned int step = 0; + + if (*index >= 32 - sizeof(union hidpp10_macro_data)) { + hidpp_log_error(&device->base, "error while parsing macro.\n"); + return -EFAULT; + } + + memcpy(macro, &memory[*index], sizeof(union hidpp10_macro_data)); + + switch (macro->any.type) { + case HIDPP10_MACRO_NOOP: + /* fallthrough */ + case HIDPP10_MACRO_WAIT_FOR_BUTTON_RELEASE: + /* fallthrough */ + case HIDPP10_MACRO_REPEAT_UNTIL_BUTTON_RELEASE: + /* fallthrough */ + case HIDPP10_MACRO_REPEAT: + step = 1; + rc = -EAGAIN; + break; + case HIDPP10_MACRO_KEY_PRESS: + /* fallthrough */ + case HIDPP10_MACRO_KEY_RELEASE: + /* fallthrough */ + case HIDPP10_MACRO_MOD_PRESS: + /* fallthrough */ + case HIDPP10_MACRO_MOD_RELEASE: + /* fallthrough */ + case HIDPP10_MACRO_MOUSE_WHEEL: + step = 2; + rc = -EAGAIN; + break; + case HIDPP10_MACRO_MOUSE_BUTTON_PRESS: + /* fallthrough */ + case HIDPP10_MACRO_MOUSE_BUTTON_RELEASE: + /* fallthrough */ + case HIDPP10_MACRO_KEY_CONSUMER_CONTROL: + /* fallthrough */ + case HIDPP10_MACRO_DELAY: + /* fallthrough */ + case HIDPP10_MACRO_JUMP: + /* fallthrough */ + case HIDPP10_MACRO_JUMP_IF_PRESSED: + step = 3; + rc = -EAGAIN; + break; + case HIDPP10_MACRO_MOUSE_POINTER_MOVE: + /* fallthrough */ + case HIDPP10_MACRO_JUMP_IF_RELEASED_TIMEOUT: + step = 5; + rc = -EAGAIN; + break; + + case HIDPP10_MACRO_END: + step = 1; + return 0; + default: + if (macro->any.type >= 0x80 && macro->any.type <= 0xFE) { + step = 1; + rc = -EAGAIN; + } else { + hidpp_log_error(&device->base, "unknown tag: 0x%02x\n", macro->any.type); + return -EFAULT; + } + } + + if ((*index + step) & 0xF0) + /* the next item will be on the following chunk */ + return -ENOMEM; + + *index += step; + + return rc; +} + +static int +hidpp10_onboard_profiles_read_macro(struct hidpp10_device *device, + uint8_t page, uint8_t offset, + union hidpp10_macro_data **return_macro) +{ + uint8_t memory[HIDPP10_PAGE_SIZE]; + union hidpp10_macro_data *macro = NULL; + unsigned count = 0; + unsigned index = 0; + uint16_t mem_index = 0; + int rc = -ENOMEM; + + do { + if (count == index) { + union hidpp10_macro_data *tmp; + + count += 32; + /* manual realloc to have the memory zero-initialized */ + tmp = zalloc(count * sizeof(union hidpp10_macro_data)); + if (macro) { + memcpy(tmp, macro, (count - 32) * sizeof(union hidpp10_macro_data)); + free(macro); + } + macro = tmp; + } + + if (rc == -ENOMEM) { + offset += mem_index; + if (offset & 0x01) + offset--; + rc = hidpp10_read_memory(device, page, offset, memory); + if (rc) + goto out_err; + mem_index &= 0x01; + + hidpp_log_buf_raw(&device->base, "-> ", memory + mem_index, 16 - mem_index); + } + + rc = hidpp10_onboard_profiles_macro_next(device, + memory, + &mem_index, + ¯o[index]); + if (rc == -EFAULT) + goto out_err; + + if (rc != -ENOMEM) { + if (macro[index].any.type == HIDPP10_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 +hidpp10_onboard_profiles_parse_macro(struct hidpp10_device *device, + uint8_t page, uint8_t offset, + union hidpp10_macro_data **return_macro) +{ + union hidpp10_macro_data *m, *macro = NULL; + unsigned i, count = 0; + int rc; + + hidpp_log_raw(&device->base, "*** macro starts at (0x%02x, 0x%04x) ***\n", page, offset); + + rc = hidpp10_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 HIDPP10_MACRO_NOOP: + hidpp_log_raw(&device->base, "noop\n"); + break; + case HIDPP10_MACRO_WAIT_FOR_BUTTON_RELEASE: + hidpp_log_raw(&device->base, "wait for button release\n"); + break; + case HIDPP10_MACRO_REPEAT_UNTIL_BUTTON_RELEASE: + hidpp_log_raw(&device->base, "repeat from beginning until button release\n"); + break; + case HIDPP10_MACRO_REPEAT: + hidpp_log_raw(&device->base, "repeat from beginning\n"); + break; + case HIDPP10_MACRO_KEY_PRESS: + hidpp_log_raw(&device->base, "key press: %02x\n", m->key.key); + break; + case HIDPP10_MACRO_KEY_RELEASE: + hidpp_log_raw(&device->base, "key release: %02x\n", m->key.key); + break; + case HIDPP10_MACRO_MOD_PRESS: + hidpp_log_raw(&device->base, "modifier press: %02x\n", m->modifier.key); + break; + case HIDPP10_MACRO_MOD_RELEASE: + hidpp_log_raw(&device->base, "modifier release: %02x\n", m->modifier.key); + break; + case HIDPP10_MACRO_MOUSE_WHEEL: + hidpp_log_raw(&device->base, "mouse wheel: %+d\n", m->wheel.value); + break; + case HIDPP10_MACRO_MOUSE_BUTTON_PRESS: + m->button.flags = ffs(hidpp_le_u16_to_cpu(m->button.flags)); + hidpp_log_raw(&device->base, "mouse button press: %d\n", m->button.flags); + break; + case HIDPP10_MACRO_MOUSE_BUTTON_RELEASE: + m->button.flags = ffs(hidpp_le_u16_to_cpu(m->button.flags)); + hidpp_log_raw(&device->base, "mouse button release: %d\n", m->button.flags); + break; + case HIDPP10_MACRO_KEY_CONSUMER_CONTROL: + m->consumer_control.key = hidpp_be_u16_to_cpu(m->consumer_control.key); + hidpp_log_raw(&device->base, "switched to consumer control: 0x%04x\n", m->consumer_control.key); + break; + case HIDPP10_MACRO_DELAY: + m->delay.time = hidpp_be_u16_to_cpu(m->delay.time); + hidpp_log_raw(&device->base, "delay: %0.03f\n", m->delay.time/1000.0); + break; + case HIDPP10_MACRO_JUMP: + /* should be skipped by hidpp10_onboard_profiles_read_macro */ + hidpp_log_raw(&device->base, "jump to: (0x%02x, 0x%02x)\n", m->jump.page, m->jump.offset); + break; + case HIDPP10_MACRO_JUMP_IF_PRESSED: + hidpp_log_raw(&device->base, "conditional jump to: (0x%02x, 0x%02x)\n", m->jump.page, m->jump.offset); + break; + case HIDPP10_MACRO_MOUSE_POINTER_MOVE: + break; + case HIDPP10_MACRO_JUMP_IF_RELEASED_TIMEOUT: + m->jump_timeout.timeout = hidpp_be_u16_to_cpu(m->jump_timeout.timeout); + hidpp_log_raw(&device->base, "conditional jump to: (0x%02x, 0x%02x) if released whithin %0.03f msecs.\n", m->jump_timeout.page, m->jump_timeout.offset, m->jump_timeout.timeout / 1000.0); + break; + case HIDPP10_MACRO_END: + break; + default: + if (m->any.type >= 0x80 && m->any.type <= 0x9F) { + m->delay.time = 8 + (m->any.type - 0x80) * 4; + m->any.type = HIDPP10_MACRO_DELAY; + hidpp_log_raw(&device->base, "short delay: %0.03f\n", m->delay.time/1000.0); + } else if (m->any.type >= 0xA0 && m->any.type <= 0xBF) { + m->delay.time = 132 + (m->any.type - 0x9F) * 8; + m->any.type = HIDPP10_MACRO_DELAY; + hidpp_log_raw(&device->base, "short delay: %0.03f\n", m->delay.time/1000.0); + } else if (m->any.type >= 0xC0 && m->any.type <= 0xDF) { + m->delay.time = 388 + (m->any.type - 0xBF) * 16; + m->any.type = HIDPP10_MACRO_DELAY; + hidpp_log_raw(&device->base, "short delay: %0.03f\n", m->delay.time/1000.0); + } else if (m->any.type >= 0xE0 && m->any.type <= 0xFE) { + m->delay.time = 900 + (m->any.type - 0xDF) * 32; + m->any.type = HIDPP10_MACRO_DELAY; + hidpp_log_raw(&device->base, "short delay: %0.03f\n", m->delay.time/1000.0); + } else { + hidpp_log_error(&device->base, "unknown tag: 0x%02x\n", m->any.type); + } + } + } + + hidpp_log_raw(&device->base, "*** end of macro ***\n"); + *return_macro = macro; + + return 0; +} + static void hidpp10_fill_buttons(struct hidpp10_device *dev, struct hidpp10_profile *profile, @@ -952,6 +1217,17 @@ hidpp10_fill_buttons(struct hidpp10_device *dev, break; case PROFILE_BUTTON_TYPE_DISABLED: break; + default: + /* macros */ + button->macro.address = i; + if (profile->macros[i]) { + free(profile->macros[i]); + profile->macros[i] = NULL; + } + hidpp10_onboard_profiles_parse_macro(dev, + b->macro.page, + b->macro.offset * 2, + &profile->macros[i]); } } } @@ -1133,16 +1409,16 @@ hidpp10_get_profile(struct hidpp10_device *dev, int8_t number, struct hidpp10_pr profile->refresh_rate = p500->usb_refresh_rate ? 1000/p500->usb_refresh_rate : 0; hidpp10_fill_dpi_modes_16(dev, profile, p500->dpi_modes, PROFILE_NUM_DPI_MODES); - hidpp10_fill_buttons(dev, profile, buttons, PROFILE_NUM_BUTTONS); hidpp10_profile_parse_names(dev, profile, number, &p500->metadata); + hidpp10_fill_buttons(dev, profile, buttons, PROFILE_NUM_BUTTONS); break; case HIDPP10_PROFILE_G700: profile->default_dpi_mode = p700->default_dpi_mode; profile->refresh_rate = p700->usb_refresh_rate ? 1000/p700->usb_refresh_rate : 0; hidpp10_fill_dpi_modes_8(dev, profile, p700->dpi_modes, PROFILE_NUM_DPI_MODES); - hidpp10_fill_buttons(dev, profile, buttons, PROFILE_NUM_BUTTONS); hidpp10_profile_parse_names(dev, profile, number, &p700->metadata); + hidpp10_fill_buttons(dev, profile, buttons, PROFILE_NUM_BUTTONS); break; default: hidpp_log_error(&dev->base, "This should never happen, complain to your maintainer.\n"); @@ -2287,11 +2563,24 @@ hidpp10_device_new(const struct hidpp_device *base, int idx, void hidpp10_device_destroy(struct hidpp10_device *dev) { + union hidpp10_macro_data **macro; + unsigned i; + if (dev->dpi_table) free(dev->dpi_table); if (dev->profile_directory) free(dev->profile_directory); - if (dev->profiles) + if (dev->profiles) { + for (i = 0; i < dev->profile_count; i++) { + ARRAY_FOR_EACH(dev->profiles[i].macros, macro) { + if (*macro) { + free(*macro); + *macro = NULL; + } + } + } + free(dev->profiles); + } free(dev); } diff --git a/src/hidpp10.h b/src/hidpp10.h index 361265fd..d621771e 100644 --- a/src/hidpp10.h +++ b/src/hidpp10.h @@ -376,6 +376,75 @@ hidpp10_get_battery_mileage(struct hidpp10_device *dev, #define PROFILE_BUTTON_SPECIAL_DPI_NEXT 0x4 #define PROFILE_BUTTON_SPECIAL_DPI_PREV 0x8 +#define HIDPP10_MACRO_NOOP 0x00 +#define HIDPP10_MACRO_WAIT_FOR_BUTTON_RELEASE 0x01 +#define HIDPP10_MACRO_REPEAT_UNTIL_BUTTON_RELEASE 0x02 +#define HIDPP10_MACRO_REPEAT 0x03 +#define HIDPP10_MACRO_KEY_PRESS 0x20 +#define HIDPP10_MACRO_KEY_RELEASE 0x21 +#define HIDPP10_MACRO_MOD_PRESS 0x22 +#define HIDPP10_MACRO_MOD_RELEASE 0x23 +#define HIDPP10_MACRO_MOUSE_WHEEL 0x24 +#define HIDPP10_MACRO_MOUSE_BUTTON_PRESS 0x40 +#define HIDPP10_MACRO_MOUSE_BUTTON_RELEASE 0x41 +#define HIDPP10_MACRO_KEY_CONSUMER_CONTROL 0x42 +#define HIDPP10_MACRO_DELAY 0x43 +#define HIDPP10_MACRO_JUMP 0x44 +#define HIDPP10_MACRO_JUMP_IF_PRESSED 0x45 +#define HIDPP10_MACRO_MOUSE_POINTER_MOVE 0x60 +#define HIDPP10_MACRO_JUMP_IF_RELEASED_TIMEOUT 0x61 +#define HIDPP10_MACRO_END 0xff + +union hidpp10_macro_data { + struct { + uint8_t type; + } __attribute__((packed)) any; + struct { + uint8_t type; /* HIDPP10_MACRO_KEY_PRESS or HIDPP10_MACRO_KEY_RELEASE */ + uint8_t key; + } __attribute__((packed)) key; + struct { + uint8_t type; /* HIDPP10_MACRO_MOD_PRESS or HIDPP10_MACRO_MOD_RELEASE */ + uint8_t key; + } __attribute__((packed)) modifier; + struct { + uint8_t type; /* HIDPP10_MACRO_MOUSE_WHEEL */ + int8_t value; + } __attribute__((packed)) wheel; + struct { + uint8_t type; /* HIDPP10_MACRO_MOUSE_BUTTON_PRESS or HIDPP10_MACRO_MOUSE_BUTTON_RELEASE */ + uint16_t flags; + } __attribute__((packed)) button; + struct { + uint8_t type; /* HIDPP10_MACRO_KEY_CONSUMER_CONTROL */ + uint16_t key; + } __attribute__((packed)) consumer_control; + struct { + uint8_t type; /* HIDPP10_MACRO_DELAY */ + uint16_t time; + } __attribute__((packed)) delay; + struct { + uint8_t type; /* HIDPP10_MACRO_JUMP or HIDPP10_MACRO_JUMP_IF_PRESSED */ + uint8_t page; + uint8_t offset; + } __attribute__((packed)) jump; + struct { + uint8_t type; /* HIDPP10_MACRO_MOUSE_POINTER_MOVE */ + int16_t x_rel; + uint16_t y_rel; + } __attribute__((packed)) pointer; + struct { + uint8_t type; /* HIDPP10_MACRO_JUMP_IF_RELEASED_TIMEOUT */ + int16_t timeout; + uint8_t page; + uint8_t offset; + } __attribute__((packed)) jump_timeout; + struct { + uint8_t type; /* HIDPP10_MACRO_END */ + } __attribute__((packed)) end; +} __attribute__((packed)); +_Static_assert(sizeof(union hidpp10_macro_data) == 5, "Invalid size"); + struct hidpp10_profile { struct { uint16_t xres; @@ -415,7 +484,11 @@ struct hidpp10_profile { struct { uint8_t type; } disabled; + struct { + uint8_t address; + } macro; } buttons[PROFILE_NUM_BUTTONS]; + union hidpp10_macro_data *macros[PROFILE_NUM_BUTTONS]; size_t num_buttons; unsigned int initialized;