diff --git a/TODO b/TODO index a276507e..ddb2ed55 100644 --- a/TODO +++ b/TODO @@ -17,3 +17,9 @@ require HID parsing in libratbag for relatively little benefit. The only advantage we really get out of it is that a configuration UI would be able to update itself if a user presses a button to e.g. change the profile while the UI is running. + +Macro support: +- enhance doctext +- add API support for name/groups +- revamp the API? +- add command to set a macro in ratbag-command diff --git a/data/etekcity.svg b/data/etekcity.svg index e467d19c..43867441 100644 --- a/data/etekcity.svg +++ b/data/etekcity.svg @@ -48,7 +48,17 @@ xlink:href="#linearGradient4449" id="linearGradient4562" gradientUnits="userSpaceOnUse" - gradientTransform="matrix(1.698701,0,0,1.698701,6.5113742,644.61147)" + gradientTransform="matrix(1.698701,0,0,1.698701,340.78058,475.26804)" + x1="31.968746" + y1="18.546629" + x2="31.968746" + y2="29.577879" /> + - + y2="200.83122" + gradientTransform="translate(0,85.407996)" /> + style="display:inline"> + style="fill:#000000;fill-opacity:1;stroke:none" + transform="translate(0,85.407996)"> @@ -192,7 +193,7 @@ width="31.607143" height="75.178574" x="274.67535" - y="179.50507" + y="264.91306" rx="3.25" ry="3.25" /> @@ -201,11 +202,10 @@ id="layer3" inkscape:label="Actions" style="display:inline" - transform="translate(647.36582,205.64997)" - sodipodi:insensitive="true"> + transform="translate(647.36582,205.64997)"> @@ -270,65 +270,58 @@ + inkscape:connector-curvature="0" + sodipodi:nodetypes="cc" /> @@ -342,21 +335,21 @@ xml:space="preserve" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:40.00000381px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:end;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:end;display:inline;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" x="27.097548" - y="-68.740425" + y="16.66757" id="text4473" sodipodi:linespacing="125%">Button0 Button1 @@ -364,21 +357,21 @@ xml:space="preserve" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:40.00000381px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" x="196.44098" - y="-136.41295" + y="-51.004951" id="text4481" sodipodi:linespacing="125%">Button2 + y="-51.004951">Button2 Button3 @@ -386,71 +379,71 @@ xml:space="preserve" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:40.00000381px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:end;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:end;display:inline;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" x="-177.07159" - y="440.89215" + y="526.30017" id="text4489" sodipodi:linespacing="125%">Button4 + y="526.30017">Button4 Button5 Button6 + x="375.72412" + y="288.81161">Button6 Button7 Button8 + x="417.32373" + y="526.42987">Button8 Button9 + transform="translate(-140.12576,-153.50496)"> + Current Profile + + + Etekcity Scroll Alpha diff --git a/data/logitech-mx_master.svg b/data/logitech-mx_master.svg index 560f6f60..ae3d9091 100644 --- a/data/logitech-mx_master.svg +++ b/data/logitech-mx_master.svg @@ -71,10 +71,10 @@ inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="0.70710678" - inkscape:cx="705.88437" - inkscape:cy="617.7106" + inkscape:cx="672.09896" + inkscape:cy="504.57351" inkscape:document-units="px" - inkscape:current-layer="layer5" + inkscape:current-layer="layer2" showgrid="false" showguides="true" inkscape:guide-bbox="true" @@ -368,4 +368,22 @@ rx="10.321068" ry="10.321068" /> + + Logitech MX Master + diff --git a/src/driver-etekcity.c b/src/driver-etekcity.c index e9f07a3e..0ec74898 100644 --- a/src/driver-etekcity.c +++ b/src/driver-etekcity.c @@ -52,6 +52,8 @@ #define ETEKCITY_CONFIG_SETTINGS 0x10 #define ETEKCITY_CONFIG_KEY_MAPPING 0x20 +#define ETEKCITY_MAX_MACRO_LENGTH 50 + struct etekcity_settings_report { uint8_t reportID; uint8_t twentyHeight; @@ -75,13 +77,13 @@ struct etekcity_macro { uint8_t heightytwo; uint8_t profile; uint8_t button_index; - uint8_t one; + uint8_t active; char name[24]; uint8_t length; struct { uint8_t keycode; uint8_t flag; - } keys[50]; + } keys[ETEKCITY_MAX_MACRO_LENGTH]; } __attribute__((packed)); struct etekcity_data { @@ -464,7 +466,7 @@ etekcity_read_profile(struct ratbag_profile *profile, unsigned int index) struct etekcity_settings_report *setting_report; uint8_t *buf; unsigned int report_rate; - unsigned int i, j; + unsigned int i; int dpi_x, dpi_y, hz; int rc; @@ -521,35 +523,6 @@ etekcity_read_profile(struct ratbag_profile *profile, unsigned int index) msleep(10); - for (i = 0; i < ETEKCITY_BUTTON_MAX; i++) { - const struct ratbag_button_action *action; - struct etekcity_macro *macro; - - action = etekcity_button_to_action(profile, i); - if (!action || action->type != RATBAG_BUTTON_ACTION_TYPE_MACRO) - continue; - - etekcity_set_config_profile(device, index, i); - macro = &drv_data->macros[index][i]; - buf = (uint8_t*)macro; - rc = ratbag_hidraw_raw_request(device, ETEKCITY_REPORT_ID_MACRO, - buf, ETEKCITY_REPORT_SIZE_MACRO, - HID_FEATURE_REPORT, HID_REQ_GET_REPORT); - log_info(device->ratbag, - "macro on button %d of profile %d is named '%s', and contains %d events:\n", - i, profile->index, - macro->name, macro->length); - for (j = 0; j < macro->length; j++) { - log_info(device->ratbag, - " - %s %s\n", - libevdev_event_code_get_name(EV_KEY, macro_mapping[macro->keys[j].keycode]), - macro->keys[j].flag & 0x80 ? "released" : "pressed"); - } - - } - - msleep(10); - if (rc < ETEKCITY_REPORT_SIZE_PROFILE) return; @@ -592,6 +565,12 @@ static void etekcity_read_button(struct ratbag_button *button) { const struct ratbag_button_action *action; + struct ratbag_device *device; + struct etekcity_macro *macro; + struct etekcity_data *drv_data; + uint8_t *buf; + unsigned j; + int rc; action = etekcity_button_to_action(button->profile, button->index); if (action) @@ -602,6 +581,104 @@ etekcity_read_button(struct ratbag_button *button) ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_KEY); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_SPECIAL); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_MACRO); + + if (action && action->type == RATBAG_BUTTON_ACTION_TYPE_MACRO) { + device = button->profile->device; + drv_data = ratbag_get_drv_data(device); + + etekcity_set_config_profile(device, + button->profile->index, + button->index); + macro = &drv_data->macros[button->profile->index][button->index]; + buf = (uint8_t*)macro; + rc = ratbag_hidraw_raw_request(device, ETEKCITY_REPORT_ID_MACRO, + buf, ETEKCITY_REPORT_SIZE_MACRO, + HID_FEATURE_REPORT, HID_REQ_GET_REPORT); + if (rc != ETEKCITY_REPORT_SIZE_MACRO) { + log_error(device->ratbag, + "Unable to retrieve the macro for button %d of profile %d: %s (%d)\n", + button->index, button->profile->index, + rc < 0 ? strerror(-rc) : "not read enough", rc); + } else { + ratbag_button_set_macro(button, macro->name); + log_raw(device->ratbag, + "macro on button %d of profile %d is named '%s', and contains %d events:\n", + button->index, button->profile->index, + macro->name, macro->length); + for (j = 0; j < macro->length; j++) { + ratbag_button_set_macro_event(button, + j, + macro->keys[j].flag & 0x80 ? RATBAG_MACRO_EVENT_KEY_RELEASED : RATBAG_MACRO_EVENT_KEY_PRESSED, + macro_mapping[macro->keys[j].keycode]); + log_raw(device->ratbag, + " - %s %s\n", + libevdev_event_code_get_name(EV_KEY, macro_mapping[macro->keys[j].keycode]), + macro->keys[j].flag & 0x80 ? "released" : "pressed"); + } + } + msleep(10); + } +} + +static int +etekcity_write_macro(struct ratbag_button *button, + const struct ratbag_button_action *action) +{ + struct ratbag_device *device; + struct etekcity_macro *macro; + struct etekcity_data *drv_data; + uint8_t *buf; + unsigned i, j, count = 0; + int rc; + + if (action->type != RATBAG_BUTTON_ACTION_TYPE_MACRO) + return 0; + + device = button->profile->device; + drv_data = ratbag_get_drv_data(device); + macro = &drv_data->macros[button->profile->index][button->index]; + buf = (uint8_t*)macro; + + for (i = 0; i < MAX_MACRO_EVENTS && count < ETEKCITY_MAX_MACRO_LENGTH; i++) { + if (action->macro->events[i].type == RATBAG_MACRO_EVENT_INVALID) + return -EINVAL; /* should not happen, ever */ + + if (action->macro->events[i].type == RATBAG_MACRO_EVENT_NONE) + break; + + /* ignore timeout events */ + if (action->macro->events[i].type == RATBAG_MACRO_EVENT_WAIT) + continue; + + for (j = 0; j < ARRAY_LENGTH(macro_mapping); j++) { + if (macro_mapping[j] == action->macro->events[i].event.key) + macro->keys[count].keycode = j; + } + if (action->macro->events[i].type == RATBAG_MACRO_EVENT_KEY_PRESSED) + macro->keys[count].flag = 0x00; + else + macro->keys[count].flag = 0x80; + count++; + } + + macro->reportID = ETEKCITY_REPORT_ID_MACRO; + macro->heightytwo = 0x82; + macro->profile = button->profile->index; + macro->button_index = button->index; + macro->active = 0x01; + strncpy(macro->name, action->macro->name, 23); + macro->length = count; + + etekcity_set_config_profile(device, + button->profile->index, + button->index); + rc = ratbag_hidraw_raw_request(device, ETEKCITY_REPORT_ID_MACRO, + buf, ETEKCITY_REPORT_SIZE_MACRO, + HID_FEATURE_REPORT, HID_REQ_SET_REPORT); + if (rc < 0) + return rc; + + return rc == ETEKCITY_REPORT_SIZE_MACRO ? 0 : -EIO; } static int @@ -622,7 +699,13 @@ etekcity_write_button(struct ratbag_button *button, *data = rc; - return 0; + rc = etekcity_write_macro(button, action); + if (rc) + log_error(device->ratbag, + "unable to write the macro to the device: '%s' (%d)\n", + strerror(-rc), rc); + + return rc; } static int diff --git a/src/libratbag-private.h b/src/libratbag-private.h index 8ff3a577..45e4a659 100644 --- a/src/libratbag-private.h +++ b/src/libratbag-private.h @@ -247,6 +247,21 @@ struct ratbag_profile { { .type = RATBAG_BUTTON_ACTION_TYPE_MACRO, \ /* FIXME: add the macro keys */ } +struct ratbag_macro_event { + enum ratbag_macro_event_type type; + union ratbag_macro_evnt { + unsigned int key; + unsigned int timeout; + } event; +}; + +#define MAX_MACRO_EVENTS 64 +struct ratbag_macro { + char *name; + char *group; + struct ratbag_macro_event events[MAX_MACRO_EVENTS]; +}; + struct ratbag_button_action { enum ratbag_button_action_type type; union ratbag_btn_action { @@ -256,8 +271,8 @@ struct ratbag_button_action { unsigned int key; /* action_type == key */ /* FIXME: modifiers */ } key; - /* FIXME: MACRO */ } action; + struct ratbag_macro *macro; /* dynamically allocated, so kept aside */ }; struct ratbag_button { @@ -338,7 +353,7 @@ ratbag_button_action_match(const struct ratbag_button_action *action, case RATBAG_BUTTON_ACTION_TYPE_SPECIAL: return match->action.special == action->action.special; case RATBAG_BUTTON_ACTION_TYPE_MACRO: - /* FIXME: currently, do nothing */ + return 1; default: break; } diff --git a/src/libratbag.c b/src/libratbag.c index fc38ec5e..6ce13f4a 100644 --- a/src/libratbag.c +++ b/src/libratbag.c @@ -1204,6 +1204,11 @@ static void ratbag_button_destroy(struct ratbag_button *button) { list_remove(&button->link); + if (button->action.macro) { + if (button->action.macro->name) + free(button->action.macro->name); + free(button->action.macro); + } free(button); } @@ -1284,3 +1289,117 @@ ratbag_resolution_get_user_data(const struct ratbag_resolution *ratbag_resolutio { return ratbag_resolution->userdata; } + +LIBRATBAG_EXPORT int +ratbag_button_set_macro(struct ratbag_button *button, const char *name) +{ + struct ratbag_macro *macro; + + if (!button->action.macro) + button->action.macro = zalloc(sizeof(*macro)); + else { + if (button->action.macro->name) + free(button->action.macro->name); + memset(button->action.macro, 0, sizeof(*macro)); + } + + button->action.type = RATBAG_BUTTON_ACTION_TYPE_MACRO; + button->action.macro->name = name ? strdup(name) : NULL; + + return 0; +} + +LIBRATBAG_EXPORT int +ratbag_button_set_macro_event(struct ratbag_button *button, + unsigned int index, + enum ratbag_macro_event_type type, + unsigned int data) +{ + struct ratbag_macro *macro; + + if (button->action.type != RATBAG_BUTTON_ACTION_TYPE_MACRO || + index >= MAX_MACRO_EVENTS) + return -EINVAL; + + macro = button->action.macro; + + switch (type) { + case RATBAG_MACRO_EVENT_KEY_PRESSED: + case RATBAG_MACRO_EVENT_KEY_RELEASED: + macro->events[index].type = type; + macro->events[index].event.key = data; + break; + case RATBAG_MACRO_EVENT_WAIT: + macro->events[index].type = type; + macro->events[index].event.timeout = data; + break; + case RATBAG_MACRO_EVENT_NONE: + macro->events[index].type = type; + break; + default: + return -EINVAL; + } + + return 0; +} + +LIBRATBAG_EXPORT int +ratbag_button_write_macro(struct ratbag_button *button) +{ + return button->profile->device->driver->write_button(button, &button->action); + +} + +LIBRATBAG_EXPORT enum ratbag_macro_event_type +ratbag_button_get_macro_event_type(struct ratbag_button *button, unsigned int index) +{ + if (button->action.type != RATBAG_BUTTON_ACTION_TYPE_MACRO || + index >= MAX_MACRO_EVENTS) + return RATBAG_MACRO_EVENT_INVALID; + + return button->action.macro->events[index].type; +} + +LIBRATBAG_EXPORT int +ratbag_button_get_macro_event_key(struct ratbag_button *button, unsigned int index) +{ + struct ratbag_macro *macro; + + if (button->action.type != RATBAG_BUTTON_ACTION_TYPE_MACRO || + index >= MAX_MACRO_EVENTS) + return -EINVAL; + + macro = button->action.macro; + + if (macro->events[index].type != RATBAG_MACRO_EVENT_KEY_PRESSED && + macro->events[index].type != RATBAG_MACRO_EVENT_KEY_RELEASED) + return -EINVAL; + + return macro->events[index].event.key; +} + +LIBRATBAG_EXPORT int +ratbag_button_get_macro_event_timeout(struct ratbag_button *button, unsigned int index) +{ + struct ratbag_macro *macro; + + if (button->action.type != RATBAG_BUTTON_ACTION_TYPE_MACRO || + index >= MAX_MACRO_EVENTS) + return -EINVAL; + + macro = button->action.macro; + + if (macro->events[index].type != RATBAG_MACRO_EVENT_WAIT) + return -EINVAL; + + return macro->events[index].event.timeout; +} + +LIBRATBAG_EXPORT const char * +ratbag_button_get_macro_name(struct ratbag_button *button) +{ + if (button->action.type != RATBAG_BUTTON_ACTION_TYPE_MACRO) + return NULL; + + return button->action.macro->name; +} diff --git a/src/libratbag.h b/src/libratbag.h index 4b6116ea..be60309d 100644 --- a/src/libratbag.h +++ b/src/libratbag.h @@ -1259,18 +1259,113 @@ ratbag_button_disable(struct ratbag_button *button); /** * @ingroup button * - * FIXME: no idea at this point + * Macro event types describing the event. */ -unsigned int -ratbag_button_get_macro(struct ratbag_button *button); +enum ratbag_macro_event_type { + RATBAG_MACRO_EVENT_INVALID = -1, + RATBAG_MACRO_EVENT_NONE = 0, + RATBAG_MACRO_EVENT_KEY_PRESSED, + RATBAG_MACRO_EVENT_KEY_RELEASED, + RATBAG_MACRO_EVENT_WAIT, +}; /** * @ingroup button * - * FIXME: no idea at this point + * If a button's action is @ref RATBAG_BUTTON_ACTION_TYPE_MACRO, + * this function returns the macro name. + * + * If the button's action type is not @ref RATBAG_BUTTON_ACTION_TYPE_MACRO, + * this function returns NULL. + * + * @param button A previously initialized ratbag button + * + * @return The name of the macro associated to the button. */ -unsigned int -ratbag_button_set_macro(struct ratbag_button *button); +const char * +ratbag_button_get_macro_name(struct ratbag_button *button); + +/** + * @ingroup button + * + * If a button's action is @ref RATBAG_BUTTON_ACTION_TYPE_MACRO, + * this function returns the macro event type configured for the event at the + * given index. + * + * If the button's action type is not @ref RATBAG_BUTTON_ACTION_TYPE_MACRO, + * this function returns @ref RATBAG_MACRO_EVENT_INVALID. + * + * @param button A previously initialized ratbag button + * @param index An index of the event within the macro we are interested in. + * + * @return The type of the event at the given index + */ +enum ratbag_macro_event_type +ratbag_button_get_macro_event_type(struct ratbag_button *button, unsigned int index); + +/** + * @ingroup button + * + * If a button's action is @ref RATBAG_BUTTON_ACTION_TYPE_MACRO, and if the + * event stored at the given index is @ref RATBAG_MACRO_EVENT_KEY_PRESSED or + * @ref RATBAG_MACRO_EVENT_KEY_RELEASED, this function returns the key code + * configured for the event at the given index. + * + * If the button's action type is not @ref RATBAG_BUTTON_ACTION_TYPE_MACRO, + * or if the event stored at the given index is not + * @ref RATBAG_MACRO_EVENT_KEY_PRESSED or @ref RATBAG_MACRO_EVENT_KEY_RELEASED, + * this function returns -EINVAL. + * + * @param button A previously initialized ratbag button + * @param index An index of the event within the macro we are interested in. + * + * @return The key of the event at the given index + */ +int +ratbag_button_get_macro_event_key(struct ratbag_button *button, unsigned int index); + +/** + * @ingroup button + * + * If a button's action is @ref RATBAG_BUTTON_ACTION_TYPE_MACRO, and if the + * event stored at the given index is @ref RATBAG_MACRO_EVENT_WAIT, this + * function returns the timeout configured for the event at the given index. + * + * If the button's action type is not @ref RATBAG_BUTTON_ACTION_TYPE_MACRO, + * or if the event stored at the given index is not + * @ref RATBAG_MACRO_EVENT_WAIT, this function returns -EINVAL. + * + * @param button A previously initialized ratbag button + * @param index An index of the event within the macro we are interested in. + * + * @return The tiemout of the event at the given index + */ +int +ratbag_button_get_macro_event_timeout(struct ratbag_button *button, unsigned int index); + +/** + * @ingroup button + * + * Sets the button's action to @ref RATBAG_BUTTON_ACTION_TYPE_MACRO and + * allocates the required memory to store the macro. + */ +int +ratbag_button_set_macro(struct ratbag_button *button, const char *name); + +int +ratbag_button_write_macro(struct ratbag_button *button); + +/** + * @ingroup button + * + * Sets the macro's event at the given index to the given type with the + * key code or timeout given. + */ +int +ratbag_button_set_macro_event(struct ratbag_button *button, + unsigned int index, + enum ratbag_macro_event_type type, + unsigned int data); /** * @ingroup button diff --git a/src/libratbag.sym b/src/libratbag.sym index bd43ce83..2726cc38 100644 --- a/src/libratbag.sym +++ b/src/libratbag.sym @@ -8,15 +8,22 @@ global: ratbag_button_get_type; ratbag_button_get_button; ratbag_button_get_key; + ratbag_button_get_macro_event_type; + ratbag_button_get_macro_event_key; + ratbag_button_get_macro_event_timeout; + ratbag_button_get_macro_name; ratbag_button_get_special; ratbag_button_get_user_data; ratbag_button_has_action_type; ratbag_button_ref; ratbag_button_set_button; ratbag_button_set_key; + ratbag_button_set_macro; + ratbag_button_set_macro_event; ratbag_button_set_special; ratbag_button_set_user_data; ratbag_button_unref; + ratbag_button_write_macro; ratbag_device_get_name; ratbag_device_get_num_buttons; ratbag_device_get_num_profiles; diff --git a/tools/ratbag-command.c b/tools/ratbag-command.c index 6652450e..560e0823 100644 --- a/tools/ratbag-command.c +++ b/tools/ratbag-command.c @@ -362,6 +362,53 @@ static const struct ratbag_cmd cmd_switch_etekcity = { .help = "Switch the Etekcity mouse active profile", }; +struct macro { + const char *name; + struct { + enum ratbag_macro_event_type type; + unsigned data; + } events[64]; +}; + +static int +str_to_macro(const char *action_arg, struct macro *m) +{ + if (!action_arg) + return -EINVAL; + + if (action_arg[0] == 'f') { + m->name = "foo"; + m->events[0].type = RATBAG_MACRO_EVENT_KEY_PRESSED; + m->events[0].data = KEY_F; + m->events[1].type = RATBAG_MACRO_EVENT_KEY_RELEASED; + m->events[1].data = KEY_F; + m->events[2].type = RATBAG_MACRO_EVENT_KEY_PRESSED; + m->events[2].data = KEY_O; + m->events[3].type = RATBAG_MACRO_EVENT_KEY_RELEASED; + m->events[3].data = KEY_O; + m->events[4].type = RATBAG_MACRO_EVENT_KEY_PRESSED; + m->events[4].data = KEY_O; + m->events[5].type = RATBAG_MACRO_EVENT_KEY_RELEASED; + m->events[5].data = KEY_O; + } else if (action_arg[0] == 'b') { + m->name = "bar"; + m->events[0].type = RATBAG_MACRO_EVENT_KEY_PRESSED; + m->events[0].data = KEY_B; + m->events[1].type = RATBAG_MACRO_EVENT_KEY_RELEASED; + m->events[1].data = KEY_B; + m->events[2].type = RATBAG_MACRO_EVENT_KEY_PRESSED; + m->events[2].data = KEY_A; + m->events[3].type = RATBAG_MACRO_EVENT_KEY_RELEASED; + m->events[3].data = KEY_A; + m->events[4].type = RATBAG_MACRO_EVENT_KEY_PRESSED; + m->events[4].data = KEY_R; + m->events[5].type = RATBAG_MACRO_EVENT_KEY_RELEASED; + m->events[5].data = KEY_R; + } + + return 0; +} + static int ratbag_cmd_change_button(struct ratbag *ratbag, uint32_t flags, int argc, char **argv) { @@ -374,6 +421,7 @@ ratbag_cmd_change_button(struct ratbag *ratbag, uint32_t flags, int argc, char * int rc = 1; unsigned int btnkey; enum ratbag_button_action_special special; + struct macro macro = {0}; int i; if (argc != 4) { @@ -402,6 +450,12 @@ ratbag_cmd_change_button(struct ratbag *ratbag, uint32_t flags, int argc, char * error("Invalid special command '%s'\n", action_arg); return 1; } + } else if (streq(action_str, "macro")) { + action_type = RATBAG_BUTTON_ACTION_TYPE_MACRO; + if (str_to_macro(action_arg, ¯o)) { + error("Invalid special command '%s'\n", action_arg); + return 1; + } } else { usage(); return 1; @@ -449,6 +503,19 @@ ratbag_cmd_change_button(struct ratbag *ratbag, uint32_t flags, int argc, char * case RATBAG_BUTTON_ACTION_TYPE_SPECIAL: rc = ratbag_button_set_special(button, special); break; + case RATBAG_BUTTON_ACTION_TYPE_MACRO: + rc = ratbag_button_set_macro(button, macro.name); + for (i = 0; i < ARRAY_LENGTH(macro.events); i++) { + if (macro.events[i].type == RATBAG_MACRO_EVENT_NONE) + break; + + ratbag_button_set_macro_event(button, + i, + macro.events[i].type, + macro.events[i].data); + } + rc = ratbag_button_write_macro(button); + break; default: error("well, that shouldn't have happened\n"); abort(); @@ -481,7 +548,7 @@ out: static const struct ratbag_cmd cmd_change_button = { .name = "change-button", .cmd = ratbag_cmd_change_button, - .args = "X ", + .args = "X ", .help = "Remap button X to the given action in the active profile", }; diff --git a/tools/shared.c b/tools/shared.c index b80b518f..280975fc 100644 --- a/tools/shared.c +++ b/tools/shared.c @@ -145,6 +145,51 @@ button_action_key_to_str(struct ratbag_button *button) return strdup(str); } +static const char *strip_ev_key(int key) +{ + const char *str = libevdev_event_code_get_name(EV_KEY, key); + + if (!strncmp(str, "KEY_", 4)) + return str + 4; + return str; +}; + +char * +button_action_macro_to_str(struct ratbag_button *button) +{ + char str[4096] = {0}; + const char *name; + int offset; + unsigned int i; + + name = ratbag_button_get_macro_name(button); + offset = snprintf(str, sizeof(str), "macro \"%s\":", + name ? name : "UNKNOWN"); + for (i = 0; i < MAX_MACRO_EVENTS; i++) { + enum ratbag_macro_event_type type = ratbag_button_get_macro_event_type(button, i); + int key = ratbag_button_get_macro_event_key(button, i); + int timeout = ratbag_button_get_macro_event_timeout(button, i); + + if (type == RATBAG_MACRO_EVENT_NONE) + break; + + switch (type) { + case RATBAG_MACRO_EVENT_KEY_PRESSED: + offset += snprintf(str + offset, sizeof(str) - offset, " %s↓", strip_ev_key(key)); + break; + case RATBAG_MACRO_EVENT_KEY_RELEASED: + offset += snprintf(str + offset, sizeof(str) - offset, " %s↑", strip_ev_key(key)); + break; + case RATBAG_MACRO_EVENT_WAIT: + offset += snprintf(str + offset, sizeof(str) - offset, " %d.%d⏱", timeout / 1000, timeout % 1000); + default: + offset += snprintf(str + offset, sizeof(str) - offset, " ###"); + } + } + + return strdup(str); +} + char * button_action_to_str(struct ratbag_button *button) { @@ -157,7 +202,7 @@ button_action_to_str(struct ratbag_button *button) case RATBAG_BUTTON_ACTION_TYPE_BUTTON: str = button_action_button_to_str(button); break; case RATBAG_BUTTON_ACTION_TYPE_KEY: str = button_action_key_to_str(button); break; case RATBAG_BUTTON_ACTION_TYPE_SPECIAL: str = strdup(button_action_special_to_str(button)); break; - case RATBAG_BUTTON_ACTION_TYPE_MACRO: str = strdup("macro"); break; + case RATBAG_BUTTON_ACTION_TYPE_MACRO: str = button_action_macro_to_str(button); break; case RATBAG_BUTTON_ACTION_TYPE_NONE: str = strdup("none"); break; default: error("type %d unknown\n", type); diff --git a/tools/shared.h b/tools/shared.h index c3c6fc8d..968131f4 100644 --- a/tools/shared.h +++ b/tools/shared.h @@ -37,6 +37,8 @@ #include #include +#define MAX_MACRO_EVENTS 64 + LIBRATBAG_ATTRIBUTE_PRINTF(1, 2) static inline void error(const char *format, ...)