mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2025-12-24 22:50:05 +01:00
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 <peter.hutterer@who-t.net>
This commit is contained in:
parent
49b5831159
commit
981f3a47e6
8 changed files with 286 additions and 3 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
20
src/evdev.c
20
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
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@
|
|||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <libevdev/libevdev.h>
|
||||
|
||||
#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.
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@
|
|||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <linux/input.h>
|
||||
|
||||
#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,
|
||||
|
|
|
|||
41
src/quirks.c
41
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;
|
||||
}
|
||||
|
|
|
|||
23
src/quirks.h
23
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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue