From 981f3a47e6ec1d10b2bac0612e86d11bb291415d Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 19 Oct 2018 12:17:03 +1000 Subject: [PATCH] quirks: add the ability to disable custom event codes/types This is a more flexible approach than adding a model flag and the C code to just call libevdev_disable_event_code(). There's a risk users will think this is is a configuration API but there are some devices out there (e.g. the Microsoft Sculpt mouse) that need a more generic solution. Case in point: the Sculpt mouse insists on holding BTN_SIDE down at all times. We cannot ship any quirks for that device because we only have the receiver's generic VID/PID. So a local override is required, but we might as well make that one generic enough to catch other devices too in the future. Signed-off-by: Peter Hutterer --- doc/user/device-quirks.rst | 4 ++ meson.build | 2 +- src/evdev.c | 20 +++++++ src/libinput-util.c | 120 +++++++++++++++++++++++++++++++++++++ src/libinput-util.h | 3 + src/quirks.c | 41 ++++++++++++- src/quirks.h | 23 ++++++- test/test-misc.c | 76 +++++++++++++++++++++++ 8 files changed, 286 insertions(+), 3 deletions(-) diff --git a/doc/user/device-quirks.rst b/doc/user/device-quirks.rst index 5531316f..f28600c4 100644 --- a/doc/user/device-quirks.rst +++ b/doc/user/device-quirks.rst @@ -177,3 +177,7 @@ AttrTPKComboLayout=below Indicates the position of the touchpad on an external touchpad+keyboard combination device. This is a string enum. Don't specify it unless the touchpad is below. +AttrEventCodeDisable=EV_ABS;BTN_STYLUS;EV_KEY:0x123; + Disables the evdev event type/code tuples on the device. Entries may be + a named event type, or a named event code, or a named event type with a + hexadecimal event code, separated by a single colon. diff --git a/meson.build b/meson.build index e3668e1e..275301cd 100644 --- a/meson.build +++ b/meson.build @@ -217,7 +217,7 @@ src_libinput_util = [ ] libinput_util = static_library('libinput-util', src_libinput_util, - dependencies : dep_udev, + dependencies : [dep_udev, dep_libevdev], include_directories : includes_include) dep_libinput_util = declare_dependency(link_with : libinput_util) diff --git a/src/evdev.c b/src/evdev.c index b2b831f2..602caccc 100644 --- a/src/evdev.c +++ b/src/evdev.c @@ -1899,6 +1899,7 @@ evdev_pre_configure_model_quirks(struct evdev_device *device) { struct quirks_context *quirks; struct quirks *q; + const struct quirk_tuples *t; char *prop; /* The Cyborg RAT has a mode button that cycles through event codes. @@ -2002,7 +2003,26 @@ evdev_pre_configure_model_quirks(struct evdev_device *device) !streq(prop, "watch")) { libevdev_disable_event_code(device->evdev, EV_MSC, MSC_TIMESTAMP); } + + if (q && quirks_get_tuples(q, QUIRK_ATTR_EVENT_CODE_DISABLE, &t)) { + int type, code; + + for (size_t i = 0; i < t->ntuples; i++) { + type = t->tuples[i].first; + code = t->tuples[i].second; + + if (code == EVENT_CODE_UNDEFINED) + libevdev_disable_event_type(device->evdev, + type); + else + libevdev_disable_event_code(device->evdev, + type, + code); + } + } + quirks_unref(q); + } static void diff --git a/src/libinput-util.c b/src/libinput-util.c index cb689029..41f9dae9 100644 --- a/src/libinput-util.c +++ b/src/libinput-util.c @@ -36,6 +36,7 @@ #include #include #include +#include #include "libinput-util.h" #include "libinput-private.h" @@ -398,6 +399,125 @@ parse_range_property(const char *prop, int *hi, int *lo) return true; } +static bool +parse_evcode_string(const char *s, int *type_out, int *code_out) +{ + int type, code; + + if (strneq(s, "EV_", 3)) { + type = libevdev_event_type_from_name(s); + if (type == -1) + return false; + + code = EVENT_CODE_UNDEFINED; + } else { + struct map { + const char *str; + int type; + } map[] = { + { "KEY_", EV_KEY }, + { "BTN_", EV_KEY }, + { "ABS_", EV_ABS }, + { "REL_", EV_REL }, + { "SW_", EV_SW }, + }; + struct map *m; + bool found = false; + + ARRAY_FOR_EACH(map, m) { + if (!strneq(s, m->str, strlen(m->str))) + continue; + + type = m->type; + code = libevdev_event_code_from_name(type, s); + if (code == -1) + return false; + + found = true; + break; + } + if (!found) + return false; + } + + *type_out = type; + *code_out = code; + + return true; +} + +/** + * Parses a string of the format "EV_ABS;KEY_A;BTN_TOOL_DOUBLETAP;ABS_X;" + * where each element must be a named event type OR a named event code OR a + * tuple in the form of EV_KEY:0x123, i.e. a named event type followed by a + * hex event code. + * + * events must point to an existing array of size nevents. + * nevents specifies the size of the array in events and returns the number + * of items, elements exceeding nevents are simply ignored, just make sure + * events is large enough for your use-case. + * + * The results are returned as input events with type and code set, all + * other fields undefined. Where only the event type is specified, the code + * is set to EVENT_CODE_UNDEFINED. + * + * On success, events contains nevents events. + */ +bool +parse_evcode_property(const char *prop, struct input_event *events, size_t *nevents) +{ + char **strv = NULL; + bool rc = false; + size_t ncodes = 0; + size_t idx; + struct input_event evs[*nevents]; + + memset(evs, 0, sizeof evs); + + strv = strv_from_string(prop, ";"); + if (!strv) + goto out; + + for (idx = 0; strv[idx]; idx++) + ncodes++; + + /* A randomly chosen max so we avoid crazy quirks */ + if (ncodes == 0 || ncodes > 32) + goto out; + + ncodes = min(*nevents, ncodes); + for (idx = 0; strv[idx]; idx++) { + char *s = strv[idx]; + + int type, code; + + if (strstr(s, ":") == NULL) { + if (!parse_evcode_string(s, &type, &code)) + goto out; + } else { + int consumed; + char stype[13] = {0}; /* EV_FF_STATUS + '\0' */ + + if (sscanf(s, "%12[A-Z_]:%x%n", stype, &code, &consumed) != 2 || + strlen(s) != (size_t)consumed || + (type = libevdev_event_type_from_name(stype)) == -1 || + code < 0 || code > libevdev_event_type_get_max(type)) + goto out; + } + + evs[idx].type = type; + evs[idx].code = code; + } + + memcpy(events, evs, ncodes * sizeof *events); + *nevents = ncodes; + rc = true; + +out: + strv_free(strv); + return rc; +} + /** * Return the next word in a string pointed to by state before the first * separator character. Call repeatedly to tokenize a whole string. diff --git a/src/libinput-util.h b/src/libinput-util.h index 360e3906..1ef4387f 100644 --- a/src/libinput-util.h +++ b/src/libinput-util.h @@ -44,6 +44,7 @@ #include #include #include +#include #include "libinput.h" @@ -426,6 +427,8 @@ int parse_mouse_wheel_click_count_property(const char *prop); bool parse_dimension_property(const char *prop, size_t *width, size_t *height); bool parse_calibration_property(const char *prop, float calibration[6]); bool parse_range_property(const char *prop, int *hi, int *lo); +#define EVENT_CODE_UNDEFINED 0xffff +bool parse_evcode_property(const char *prop, struct input_event *events, size_t *nevents); enum tpkbcombo_layout { TPKBCOMBO_LAYOUT_UNKNOWN, diff --git a/src/quirks.c b/src/quirks.c index 8b400fb7..b163d099 100644 --- a/src/quirks.c +++ b/src/quirks.c @@ -57,6 +57,7 @@ enum property_type { PT_DIMENSION, PT_RANGE, PT_DOUBLE, + PT_TUPLES, }; /** @@ -75,9 +76,10 @@ struct property { uint32_t u; int32_t i; char *s; + double d; struct quirk_dimensions dim; struct quirk_range range; - double d; + struct quirk_tuples tuples; } value; }; @@ -273,6 +275,7 @@ quirk_get_name(enum quirk q) case QUIRK_ATTR_USE_VELOCITY_AVERAGING: return "AttrUseVelocityAveraging"; case QUIRK_ATTR_THUMB_SIZE_THRESHOLD: return "AttrThumbSizeThreshold"; case QUIRK_ATTR_MSC_TIMESTAMP: return "AttrMscTimestamp"; + case QUIRK_ATTR_EVENT_CODE_DISABLE: return "AttrEventCodeDisable"; default: abort(); } @@ -726,6 +729,22 @@ parse_attr(struct quirks_context *ctx, goto out; p->type = PT_STRING; p->value.s = safe_strdup(value); + rc = true; + } else if (streq(key, quirk_get_name(QUIRK_ATTR_EVENT_CODE_DISABLE))) { + size_t nevents = 32; + struct input_event events[nevents]; + p->id = QUIRK_ATTR_EVENT_CODE_DISABLE; + if (!parse_evcode_property(value, events, &nevents) || + nevents == 0) + goto out; + + for (size_t i = 0; i < nevents; i++) { + p->value.tuples.tuples[i].first = events[i].type; + p->value.tuples.tuples[i].second = events[i].code; + } + p->value.tuples.ntuples = nevents; + p->type = PT_TUPLES; + rc = true; } else { qlog_error(ctx, "Unknown key %s in %s\n", key, s->name); @@ -1543,3 +1562,23 @@ quirks_get_range(struct quirks *q, return true; } + +bool +quirks_get_tuples(struct quirks *q, + enum quirk which, + const struct quirk_tuples **tuples) +{ + struct property *p; + + if (!q) + return false; + + p = quirk_find_prop(q, which); + if (!p) + return false; + + assert(p->type == PT_TUPLES); + *tuples = &p->value.tuples; + + return true; +} diff --git a/src/quirks.h b/src/quirks.h index d032c8a7..c8898739 100644 --- a/src/quirks.h +++ b/src/quirks.h @@ -50,6 +50,14 @@ struct quirk_range { int lower, upper; }; +struct quirk_tuples { + struct { + int first; + int second; + } tuples[32]; + size_t ntuples; +}; + /** * Quirks known to libinput */ @@ -102,7 +110,7 @@ enum quirk { QUIRK_ATTR_USE_VELOCITY_AVERAGING, QUIRK_ATTR_THUMB_SIZE_THRESHOLD, QUIRK_ATTR_MSC_TIMESTAMP, - + QUIRK_ATTR_EVENT_CODE_DISABLE, _QUIRK_LAST_ATTR_QUIRK_, /* Guard: do not modify */ }; @@ -293,3 +301,16 @@ bool quirks_get_range(struct quirks *q, enum quirk which, struct quirk_range *val); + +/** + * Get the tuples of the given quirk. + * This function will assert if the quirk type does not match the + * requested type. If the quirk is not set for this device, tuples is + * unchanged. + * + * @return true if the quirk value is valid, false otherwise. + */ +bool +quirks_get_tuples(struct quirks *q, + enum quirk which, + const struct quirk_tuples **tuples); diff --git a/test/test-misc.c b/test/test-misc.c index 0946c180..4589509d 100644 --- a/test/test-misc.c +++ b/test/test-misc.c @@ -1013,6 +1013,81 @@ START_TEST(range_prop_parser) } END_TEST +START_TEST(evcode_prop_parser) +{ + struct parser_test_tuple { + const char *prop; + bool success; + size_t ntuples; + int tuples[20]; + } tests[] = { + { "EV_KEY", true, 1, {EV_KEY, 0xffff} }, + { "EV_ABS;", true, 1, {EV_ABS, 0xffff} }, + { "ABS_X;", true, 1, {EV_ABS, ABS_X} }, + { "SW_TABLET_MODE;", true, 1, {EV_SW, SW_TABLET_MODE} }, + { "EV_SW", true, 1, {EV_SW, 0xffff} }, + { "ABS_Y", true, 1, {EV_ABS, ABS_Y} }, + { "EV_ABS:0x00", true, 1, {EV_ABS, ABS_X} }, + { "EV_ABS:01", true, 1, {EV_ABS, ABS_Y} }, + { "ABS_TILT_X;ABS_TILT_Y;", true, 2, + { EV_ABS, ABS_TILT_X, + EV_ABS, ABS_TILT_Y} }, + { "BTN_TOOL_DOUBLETAP;EV_KEY;KEY_A", true, 3, + { EV_KEY, BTN_TOOL_DOUBLETAP, + EV_KEY, 0xffff, + EV_KEY, KEY_A } }, + { "REL_Y;ABS_Z;BTN_STYLUS", true, 3, + { EV_REL, REL_Y, + EV_ABS, ABS_Z, + EV_KEY, BTN_STYLUS } }, + { "REL_Y;EV_KEY:0x123;BTN_STYLUS", true, 3, + { EV_REL, REL_Y, + EV_KEY, 0x123, + EV_KEY, BTN_STYLUS } }, + { .prop = "", .success = false }, + { .prop = "EV_FOO", .success = false }, + { .prop = "EV_KEY;EV_FOO", .success = false }, + { .prop = "BTN_STYLUS;EV_FOO", .success = false }, + { .prop = "BTN_UNKNOWN", .success = false }, + { .prop = "BTN_UNKNOWN;EV_KEY", .success = false }, + { .prop = "PR_UNKNOWN", .success = false }, + { .prop = "BTN_STYLUS;PR_UNKNOWN;ABS_X", .success = false }, + { .prop = "EV_REL:0xffff", .success = false }, + { .prop = "EV_REL:0x123.", .success = false }, + { .prop = "EV_REL:ffff", .success = false }, + { .prop = "EV_REL:blah", .success = false }, + { .prop = "KEY_A:0x11", .success = false }, + { .prop = "EV_KEY:0x11 ", .success = false }, + { .prop = "EV_KEY:0x11not", .success = false }, + { .prop = "none", .success = false }, + { .prop = NULL }, + }; + struct parser_test_tuple *t = tests; + + for (int i = 0; tests[i].prop; i++) { + bool success; + size_t nevents = 32; + struct input_event events[nevents]; + + t = &tests[i]; + success = parse_evcode_property(t->prop, events, &nevents); + ck_assert(success == t->success); + if (!success) + continue; + + ck_assert_int_eq(nevents, t->ntuples); + for (size_t j = 0; j < nevents; j++) { + int type, code; + + type = events[j].type; + code = events[j].code; + ck_assert_int_eq(t->tuples[j * 2], type); + ck_assert_int_eq(t->tuples[j * 2 + 1], code); + } + } +} +END_TEST + START_TEST(time_conversion) { ck_assert_int_eq(us(10), 10); @@ -1728,6 +1803,7 @@ TEST_COLLECTION(misc) litest_add_deviceless("misc:parser", reliability_prop_parser); litest_add_deviceless("misc:parser", calibration_prop_parser); litest_add_deviceless("misc:parser", range_prop_parser); + litest_add_deviceless("misc:parser", evcode_prop_parser); litest_add_deviceless("misc:parser", safe_atoi_test); litest_add_deviceless("misc:parser", safe_atoi_base_16_test); litest_add_deviceless("misc:parser", safe_atoi_base_8_test); -- 2.34.1