From 4738c5cde5a2c2c45772501b7952ad870c207aae Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Thu, 24 May 2018 14:47:30 +1000 Subject: [PATCH 01/14] test: make litest_copy_file copy normal files too Make the tempfile creation dependent on whether the required template is present. Currently unused, this is just prep work for future patches. Signed-off-by: Peter Hutterer --- test/litest.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/test/litest.c b/test/litest.c index b72ec444..0e050789 100644 --- a/test/litest.c +++ b/test/litest.c @@ -1104,13 +1104,20 @@ litest_copy_file(const char *dest, const char *src, const char *header) { int in, out, length; struct created_file *file; - int suffixlen; file = zalloc(sizeof(*file)); file->path = safe_strdup(dest); - suffixlen = file->path + strlen(file->path) - rindex(file->path, '.'); - out = mkstemps(file->path, suffixlen); + if (strstr(dest, "XXXXXX")) { + int suffixlen; + + suffixlen = file->path + + strlen(file->path) - + rindex(file->path, '.'); + out = mkstemps(file->path, suffixlen); + } else { + out = open(file->path, O_CREAT|O_WRONLY); + } if (out == -1) litest_abort_msg("Failed to write to file %s (%s)\n", file->path, From c81809d0aa3c454d48862c28be2ac032fc5bf1ec Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Thu, 24 May 2018 14:49:22 +1000 Subject: [PATCH 02/14] test: remove created directories too If we created it, remove it again. No change because we're not adding any of the directories yet. Signed-off-by: Peter Hutterer --- test/litest.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/litest.c b/test/litest.c index 0e050789..9d711098 100644 --- a/test/litest.c +++ b/test/litest.c @@ -741,6 +741,7 @@ litest_signal(int sig) list_for_each_safe(f, tmp, &created_files_list, link) { list_remove(&f->link); unlink(f->path); + rmdir(f->path); /* in the sighandler, we can't free */ } @@ -1219,6 +1220,7 @@ litest_remove_udev_rules(struct list *created_files_list) list_for_each_safe(f, tmp, created_files_list, link) { list_remove(&f->link); unlink(f->path); + rmdir(f->path); free(f->path); free(f); } From e05fa8444a01c87395dae77844ac7e1352bd75b9 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Wed, 23 May 2018 15:21:56 +1000 Subject: [PATCH 03/14] util: add a list_append() Signed-off-by: Peter Hutterer --- src/libinput-util.c | 14 ++++++++++ src/libinput-util.h | 1 + test/test-misc.c | 64 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+) diff --git a/src/libinput-util.c b/src/libinput-util.c index eb6e3ecf..a475c465 100644 --- a/src/libinput-util.c +++ b/src/libinput-util.c @@ -61,6 +61,20 @@ list_insert(struct list *list, struct list *elm) elm->next->prev = elm; } +void +list_append(struct list *list, struct list *elm) +{ + assert((list->next != NULL && list->prev != NULL) || + !"list->next|prev is NULL, possibly missing list_init()"); + assert(((elm->next == NULL && elm->prev == NULL) || list_empty(elm)) || + !"elm->next|prev is not NULL, list node used twice?"); + + elm->next = list; + elm->prev = list->prev; + list->prev = elm; + elm->prev->next = elm; +} + void list_remove(struct list *elm) { diff --git a/src/libinput-util.h b/src/libinput-util.h index 92e7cf73..6524d940 100644 --- a/src/libinput-util.h +++ b/src/libinput-util.h @@ -86,6 +86,7 @@ struct list { void list_init(struct list *list); void list_insert(struct list *list, struct list *elm); +void list_append(struct list *list, struct list *elm); void list_remove(struct list *elm); bool list_empty(const struct list *list); diff --git a/test/test-misc.c b/test/test-misc.c index ea262f65..779baf31 100644 --- a/test/test-misc.c +++ b/test/test-misc.c @@ -1580,6 +1580,67 @@ START_TEST(timer_flush) } END_TEST +START_TEST(list_test_insert) +{ + struct list_test { + int val; + struct list node; + } tests[] = { + { .val = 1 }, + { .val = 2 }, + { .val = 3 }, + { .val = 4 }, + }; + struct list_test *t; + struct list head; + int val; + + list_init(&head); + + ARRAY_FOR_EACH(tests, t) { + list_insert(&head, &t->node); + } + + val = 4; + list_for_each(t, &head, node) { + ck_assert_int_eq(t->val, val); + val--; + } + + ck_assert_int_eq(val, 0); +} +END_TEST + +START_TEST(list_test_append) +{ + struct list_test { + int val; + struct list node; + } tests[] = { + { .val = 1 }, + { .val = 2 }, + { .val = 3 }, + { .val = 4 }, + }; + struct list_test *t; + struct list head; + int val; + + list_init(&head); + + ARRAY_FOR_EACH(tests, t) { + list_append(&head, &t->node); + } + + val = 1; + list_for_each(t, &head, node) { + ck_assert_int_eq(t->val, val); + val++; + } + ck_assert_int_eq(val, 5); +} +END_TEST + TEST_COLLECTION(misc) { litest_add_no_device("events:conversion", event_conversion_device_notify); @@ -1623,4 +1684,7 @@ TEST_COLLECTION(misc) litest_add_no_device("misc:fd", fd_no_event_leak); litest_add_no_device("misc:library_version", library_version); + + litest_add_no_device("misc:list", list_test_insert); + litest_add_no_device("misc:list", list_test_append); } From be647fbb0d32184636bd4bdd41e899b53ef59509 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 1 Jun 2018 10:21:18 +1000 Subject: [PATCH 04/14] util: add a safe_atou() as unsigned equivalent to safe_atoi() Signed-off-by: Peter Hutterer --- src/libinput-util.h | 30 ++++++++++++ test/test-misc.c | 115 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) diff --git a/src/libinput-util.h b/src/libinput-util.h index 6524d940..955d1ae7 100644 --- a/src/libinput-util.h +++ b/src/libinput-util.h @@ -516,6 +516,36 @@ safe_atoi(const char *str, int *val) return safe_atoi_base(str, val, 10); } +static inline bool +safe_atou_base(const char *str, unsigned int *val, int base) +{ + char *endptr; + unsigned long v; + + assert(base == 10 || base == 16 || base == 8); + + errno = 0; + v = strtoul(str, &endptr, base); + if (errno > 0) + return false; + if (str == endptr) + return false; + if (*str != '\0' && *endptr != '\0') + return false; + + if (v > UINT_MAX) + return false; + + *val = v; + return true; +} + +static inline bool +safe_atou(const char *str, unsigned int *val) +{ + return safe_atou_base(str, val, 10); +} + static inline bool safe_atod(const char *str, double *val) { diff --git a/test/test-misc.c b/test/test-misc.c index 779baf31..08aa1c8b 100644 --- a/test/test-misc.c +++ b/test/test-misc.c @@ -1198,6 +1198,118 @@ START_TEST(safe_atoi_base_8_test) } END_TEST +struct atou_test { + char *str; + bool success; + unsigned int val; +}; + +START_TEST(safe_atou_test) +{ + struct atou_test tests[] = { + { "10", true, 10 }, + { "20", true, 20 }, + { "-1", false, 0 }, + { "2147483647", true, 2147483647 }, + { "-2147483648", false, 0}, + { "4294967295", true, 4294967295 }, + { "0x0", false, 0 }, + { "-10x10", false, 0 }, + { "1x-99", false, 0 }, + { "", false, 0 }, + { "abd", false, 0 }, + { "xabd", false, 0 }, + { "0xaf", false, 0 }, + { "0x0x", false, 0 }, + { "x10", false, 0 }, + { NULL, false, 0 } + }; + unsigned int v; + bool success; + + for (int i = 0; tests[i].str != NULL; i++) { + v = 0xad; + success = safe_atou(tests[i].str, &v); + ck_assert(success == tests[i].success); + if (success) + ck_assert_int_eq(v, tests[i].val); + else + ck_assert_int_eq(v, 0xad); + } +} +END_TEST + +START_TEST(safe_atou_base_16_test) +{ + struct atou_test tests[] = { + { "10", true, 0x10 }, + { "20", true, 0x20 }, + { "-1", false, 0 }, + { "0x10", true, 0x10 }, + { "0xff", true, 0xff }, + { "abc", true, 0xabc }, + { "-10", false, 0 }, + { "0x0", true, 0 }, + { "0", true, 0 }, + { "0x-99", false, 0 }, + { "0xak", false, 0 }, + { "0x", false, 0 }, + { "x10", false, 0 }, + { NULL, false, 0 } + }; + + unsigned int v; + bool success; + + for (int i = 0; tests[i].str != NULL; i++) { + v = 0xad; + success = safe_atou_base(tests[i].str, &v, 16); + ck_assert(success == tests[i].success); + if (success) + ck_assert_int_eq(v, tests[i].val); + else + ck_assert_int_eq(v, 0xad); + } +} +END_TEST + +START_TEST(safe_atou_base_8_test) +{ + struct atou_test tests[] = { + { "7", true, 07 }, + { "10", true, 010 }, + { "20", true, 020 }, + { "-1", false, 0 }, + { "010", true, 010 }, + { "0ff", false, 0 }, + { "abc", false, 0}, + { "0xabc", false, 0}, + { "-10", false, 0 }, + { "0", true, 0 }, + { "00", true, 0 }, + { "0x0", false, 0 }, + { "0x-99", false, 0 }, + { "0xak", false, 0 }, + { "0x", false, 0 }, + { "x10", false, 0 }, + { NULL, false, 0 } + }; + + unsigned int v; + bool success; + + for (int i = 0; tests[i].str != NULL; i++) { + v = 0xad; + success = safe_atou_base(tests[i].str, &v, 8); + ck_assert(success == tests[i].success); + if (success) + ck_assert_int_eq(v, tests[i].val); + else + ck_assert_int_eq(v, 0xad); + } +} +END_TEST + struct atod_test { char *str; bool success; @@ -1675,6 +1787,9 @@ TEST_COLLECTION(misc) litest_add_no_device("misc:parser", safe_atoi_test); litest_add_no_device("misc:parser", safe_atoi_base_16_test); litest_add_no_device("misc:parser", safe_atoi_base_8_test); + litest_add_no_device("misc:parser", safe_atou_test); + litest_add_no_device("misc:parser", safe_atou_base_16_test); + litest_add_no_device("misc:parser", safe_atou_base_8_test); litest_add_no_device("misc:parser", safe_atod_test); litest_add_no_device("misc:parser", strsplit_test); litest_add_no_device("misc:parser", kvsplit_double_test); From 5792af9a5f4ae0bc11cd6d070ed643896c157f19 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Mon, 21 May 2018 14:28:53 +1000 Subject: [PATCH 05/14] Implement a quirks system to replace the udev property parsing Previously, we had all extra device information ("This is an Apple Touchpad", "This touchpad causes pointer jumps", etc.) in the udev hwdb. The problem with the hwdb is that updating it is nontrivial for the average user and debugging when things go wrong is even harder. Plus, the hwdb has a matching scheme that is unpredictable unless one is familiar with the implementation. This patch set moves the hwdb entries into .ini style text files, with a simple line-based parser. A new libinput list-quirks tool can list the quirks applied to any given device, in --verbose mode it prints all matches as they apply or not apply. The data files are currently unused by libinput, that comes in a later patch. They're installed though, the defaults point to the /usr/share/libinput directory and for *temporary* local overrides the single file /etc/libinput/local-overrides.quirks. Failure to parse any file is a hard failure for the quirks system, but if the local override file doesn't exist that's fine. THIS IS NOT A CONFIGURATION INTERFACE! None of these settings are exposed via the libinput_device_config_* calls. There is no API guarantee for these files, think of them as source code. Signed-off-by: Peter Hutterer --- data/10-generic-keyboard.quirks | 4 + data/10-generic-lid.quirks | 9 + data/10-generic-trackball.quirks | 3 + data/30-vendor-aiptek.quirks | 5 + data/30-vendor-alps.quirks | 9 + data/30-vendor-cyapa.quirks | 3 + data/30-vendor-elantech.quirks | 4 + data/30-vendor-huion.quirks | 12 + data/30-vendor-ibm.quirks | 39 + data/30-vendor-logitech.quirks | 44 + data/30-vendor-microsoft.quirks | 16 + data/30-vendor-razer.quirks | 11 + data/30-vendor-synaptics.quirks | 6 + data/30-vendor-wacom.quirks | 12 + data/50-system-apple.quirks | 48 + data/50-system-asus.quirks | 9 + data/50-system-chicony.quirks | 7 + data/50-system-cyborg.quirks | 6 + data/50-system-dell.quirks | 15 + data/50-system-google.quirks | 86 ++ data/50-system-hp.quirks | 24 + data/50-system-lenovo.quirks | 78 ++ data/50-system-system76.quirks | 19 + data/README.md | 78 ++ doc/device-quirks.dox | 102 +++ doc/page-hierarchy.dox | 1 + meson.build | 72 +- src/quirks.c | 1437 ++++++++++++++++++++++++++++++ src/quirks.h | 273 ++++++ test/test-quirks.c | 809 +++++++++++++++++ tools/libinput-list-quirks.c | 245 +++++ tools/libinput-list-quirks.man | 28 + 32 files changed, 3512 insertions(+), 2 deletions(-) create mode 100644 data/10-generic-keyboard.quirks create mode 100644 data/10-generic-lid.quirks create mode 100644 data/10-generic-trackball.quirks create mode 100644 data/30-vendor-aiptek.quirks create mode 100644 data/30-vendor-alps.quirks create mode 100644 data/30-vendor-cyapa.quirks create mode 100644 data/30-vendor-elantech.quirks create mode 100644 data/30-vendor-huion.quirks create mode 100644 data/30-vendor-ibm.quirks create mode 100644 data/30-vendor-logitech.quirks create mode 100644 data/30-vendor-microsoft.quirks create mode 100644 data/30-vendor-razer.quirks create mode 100644 data/30-vendor-synaptics.quirks create mode 100644 data/30-vendor-wacom.quirks create mode 100644 data/50-system-apple.quirks create mode 100644 data/50-system-asus.quirks create mode 100644 data/50-system-chicony.quirks create mode 100644 data/50-system-cyborg.quirks create mode 100644 data/50-system-dell.quirks create mode 100644 data/50-system-google.quirks create mode 100644 data/50-system-hp.quirks create mode 100644 data/50-system-lenovo.quirks create mode 100644 data/50-system-system76.quirks create mode 100644 data/README.md create mode 100644 doc/device-quirks.dox create mode 100644 src/quirks.c create mode 100644 src/quirks.h create mode 100644 test/test-quirks.c create mode 100644 tools/libinput-list-quirks.c create mode 100644 tools/libinput-list-quirks.man diff --git a/data/10-generic-keyboard.quirks b/data/10-generic-keyboard.quirks new file mode 100644 index 00000000..3063dad6 --- /dev/null +++ b/data/10-generic-keyboard.quirks @@ -0,0 +1,4 @@ +[Serial Keyboards] +MatchUdevType=keyboard +MatchBus=ps2 +AttrKeyboardIntegration=internal diff --git a/data/10-generic-lid.quirks b/data/10-generic-lid.quirks new file mode 100644 index 00000000..f3748e16 --- /dev/null +++ b/data/10-generic-lid.quirks @@ -0,0 +1,9 @@ +[Lid Switch Ct9] +MatchName=*Lid Switch* +MatchDMIModalias=dmi:*:ct9:* +AttrLidSwitchReliability=reliable + +[Lid Switch Ct10] +MatchName=*Lid Switch* +MatchDMIModalias=dmi:*:ct10:* +AttrLidSwitchReliability=reliable diff --git a/data/10-generic-trackball.quirks b/data/10-generic-trackball.quirks new file mode 100644 index 00000000..a554a44d --- /dev/null +++ b/data/10-generic-trackball.quirks @@ -0,0 +1,3 @@ +[Trackball] +MatchName=*Trackball* +ModelTrackball=1 diff --git a/data/30-vendor-aiptek.quirks b/data/30-vendor-aiptek.quirks new file mode 100644 index 00000000..24abb134 --- /dev/null +++ b/data/30-vendor-aiptek.quirks @@ -0,0 +1,5 @@ +[Aiptek No Tilt Tablet] +MatchUdevType=tablet +MatchBus=usb +MatchVendor=0x08CA +ModelTabletNoTilt=1 diff --git a/data/30-vendor-alps.quirks b/data/30-vendor-alps.quirks new file mode 100644 index 00000000..706a3f7a --- /dev/null +++ b/data/30-vendor-alps.quirks @@ -0,0 +1,9 @@ +[AlpsTouchpadDualPoint] +MatchUdevType=touchpad +MatchName=*AlpsPS/2 ALPS DualPoint TouchPad +ModelALPSTouchpad=1 + +[AlpsTouchpadGlidePoint] +MatchUdevType=touchpad +MatchName=*AlpsPS/2 ALPS GlidePoint +ModelALPSTouchpad=1 diff --git a/data/30-vendor-cyapa.quirks b/data/30-vendor-cyapa.quirks new file mode 100644 index 00000000..b58a5541 --- /dev/null +++ b/data/30-vendor-cyapa.quirks @@ -0,0 +1,3 @@ +[Cyapa Touchpads] +MatchName=*Cypress APA Trackpad ?cyapa? +AttrPressureRange=10:8 diff --git a/data/30-vendor-elantech.quirks b/data/30-vendor-elantech.quirks new file mode 100644 index 00000000..59a8157f --- /dev/null +++ b/data/30-vendor-elantech.quirks @@ -0,0 +1,4 @@ +[Elantech Touchpads] +MatchName=*Elantech Touchpad* +AttrResolutionHint=31x31 +AttrPressureRange=10:8 diff --git a/data/30-vendor-huion.quirks b/data/30-vendor-huion.quirks new file mode 100644 index 00000000..65f3b2c1 --- /dev/null +++ b/data/30-vendor-huion.quirks @@ -0,0 +1,12 @@ +# HUION PenTablet device. Some of these devices send a BTN_TOOL_PEN event +# with value 1 on the first event received by the device but never send the +# matching BTN_TOOL_PEN value 0 event. The device appears as if it was +# permanently in proximity. +# +# HUION re-uses USB IDs for its devices, not every HUION tablet is +# affected by this bug, libinput will auto-disable this feature +[HUION PenTablet] +MatchUdevType=tablet +MatchBus=usb +MatchVendor=0x256C +ModelTabletNoProximityOut=1 diff --git a/data/30-vendor-ibm.quirks b/data/30-vendor-ibm.quirks new file mode 100644 index 00000000..195fc21a --- /dev/null +++ b/data/30-vendor-ibm.quirks @@ -0,0 +1,39 @@ +# IBM/Lenovo Scrollpoint mouse. Instead of a scroll wheel these mice +# feature trackpoint-like sticks which generate a huge amount of scroll +# events that need to be handled differently than scroll wheel events + +[IBM ScrollPoint Mouse 3100] +MatchUdevType=mouse +MatchVendor=0x04B3 +MatchProduct=0x3100 +ModelLenovoScrollPoint=1 + +[IBM ScrollPoint Mouse 3103] +MatchUdevType=mouse +MatchVendor=0x04B3 +MatchProduct=0x3103 +ModelLenovoScrollPoint=1 + +[IBM ScrollPoint Mouse 3105] +MatchUdevType=mouse +MatchVendor=0x04B3 +MatchProduct=0x3105 +ModelLenovoScrollPoint=1 + +[IBM ScrollPoint Mouse 3108] +MatchUdevType=mouse +MatchVendor=0x04B3 +MatchProduct=0x3108 +ModelLenovoScrollPoint=1 + +[IBM ScrollPoint Mouse 3109] +MatchUdevType=mouse +MatchVendor=0x04B3 +MatchProduct=0x3109 +ModelLenovoScrollPoint=1 + +[IBM ScrollPoint Mouse 6049] +MatchUdevType=mouse +MatchVendor=0x17EF +MatchProduct=0x6049 +ModelLenovoScrollPoint=1 diff --git a/data/30-vendor-logitech.quirks b/data/30-vendor-logitech.quirks new file mode 100644 index 00000000..ebbc9cc2 --- /dev/null +++ b/data/30-vendor-logitech.quirks @@ -0,0 +1,44 @@ +[Logitech M570] +MatchName=*Logitech M570* +ModelTrackball=1 + +[Logitech Marble Mouse Trackball] +MatchUdevType=mouse +MatchBus=usb +MatchVendor=0x46D +MatchProduct=0xC408 +ModelLogitechMarbleMouse=1 + +[Logitech K400] +MatchUdevType=mouse +MatchBus=usb +MatchVendor=0x046D +MatchProduct=0x4024 +ModelBouncingKeys=1 + +[Logitech K400r] +MatchUdevType=mouse +MatchBus=usb +MatchVendor=0x046D +MatchProduct=0x404B +ModelBouncingKeys=1 + +[Logitech K830] +MatchUdevType=mouse +MatchBus=usb +MatchVendor=0x046D +MatchProduct=0x404C +ModelBouncingKeys=1 + +[Logitech K400Plus] +MatchUdevType=mouse +MatchBus=usb +MatchVendor=0x046D +MatchProduct=0x404D +ModelBouncingKeys=1 + +[Logitech Wireless Touchpad] +MatchBus=usb +MatchVendor=0x046D +MatchProduct=0x4011 +AttrPalmPressureThreshold=400 diff --git a/data/30-vendor-microsoft.quirks b/data/30-vendor-microsoft.quirks new file mode 100644 index 00000000..c38f30dd --- /dev/null +++ b/data/30-vendor-microsoft.quirks @@ -0,0 +1,16 @@ +[Microsoft Surface 3 Lid Switch] +MatchName=*Lid Switch* +MatchDMIModalias=dmi:*svnMicrosoftCorporation:pnSurface3:* +AttrLidSwitchReliability=write_open + +[Microsoft Surface 3 Type Cover Keyboard] +MatchName=*Microsoft Surface Type Cover Keyboard* +MatchDMIModalias=dmi:*svnMicrosoftCorporation:pnSurface3:* +AttrKeyboardIntegration=internal + +[Microsoft Nano Transceiver v2.0] +MatchUdevType=mouse +MatchBus=usb +MatchVendor=0x045E +MatchProduct=0x8000 +ModelBouncingKeys=1 diff --git a/data/30-vendor-razer.quirks b/data/30-vendor-razer.quirks new file mode 100644 index 00000000..f69e1e54 --- /dev/null +++ b/data/30-vendor-razer.quirks @@ -0,0 +1,11 @@ +[Razer Blade Keyboard] +MatchUdevType=keyboard +MatchBus=usb +MatchVendor=0x1532 +MatchProduct=0x0220 +AttrKeyboardIntegration=internal + +[Razer Blade Lid Switch] +MatchName=*Lid Switch* +MatchDMIModalias=dmi:*svnRazer:pnBlade* +AttrLidSwitchReliability=write_open diff --git a/data/30-vendor-synaptics.quirks b/data/30-vendor-synaptics.quirks new file mode 100644 index 00000000..ffb7cd58 --- /dev/null +++ b/data/30-vendor-synaptics.quirks @@ -0,0 +1,6 @@ +[Synaptics Serial Touchpads] +MatchUdevType=touchpad +MatchBus=ps2 +MatchVendor=0x0002 +MatchProduct=0x0007 +ModelSynapticsSerialTouchpad=1 diff --git a/data/30-vendor-wacom.quirks b/data/30-vendor-wacom.quirks new file mode 100644 index 00000000..87a2eeca --- /dev/null +++ b/data/30-vendor-wacom.quirks @@ -0,0 +1,12 @@ +[Wacom Touchpads] +MatchUdevType=touchpad +MatchBus=usb +MatchVendor=0x056A +ModelWacomTouchpad=1 + +[Wacom Intuos Pro PTH660] +MatchUdevType=touchpad +MatchBus=usb +MatchVendor=0x056A +MatchProduct=0x0357 +AttrPalmSizeThreshold=1 diff --git a/data/50-system-apple.quirks b/data/50-system-apple.quirks new file mode 100644 index 00000000..0b1552a0 --- /dev/null +++ b/data/50-system-apple.quirks @@ -0,0 +1,48 @@ +[Apple Touchpads USB] +MatchVendor=0x05AC +MatchBus=usb +MatchUdevType=touchpad +ModelAppleTouchpad=1 +AttrSizeHint=104x75 +AttrTouchSizeRange=150:130 +AttrPalmSizeThreshold=800 + +[Apple Touchpads Bluetooth] +MatchVendor=0x05AC +MatchBus=bluetooth +MatchUdevType=touchpad +ModelAppleTouchpad=1 + +[Apple Internal Keyboard] +MatchName=*Apple Inc. Apple Internal Keyboard* +AttrKeyboardIntegration=internal + +[Apple MagicMouse] +MatchUdevType=mouse +MatchBus=bluetooth +MatchVendor=0x05AC +MatchProduct=0x030D +ModelAppleMagicMouse=1 + +[Apple Magic Trackpad v1 (2010, clickpad)] +MatchUdevType=touchpad +MatchBus=bluetooth +MatchVendor=0x5AC +MatchProduct=0x030E +AttrSizeHint=130x110 +AttrTouchSizeRange=20:10 +AttrPalmSizeThreshold=900 + +[Apple Touchpad OneButton] +MatchUdevType=touchpad +MatchBus=usb +MatchVendor=0x5AC +MatchProduct=0x021A +ModelAppleTouchpadOneButton=1 + +[Apple Touchpad MacbookPro5,5] +MatchUdevType=touchpad +MatchBus=usb +MatchVendor=0x05AC +MatchProduct=0x0237 +AttrPalmSizeThreshold=1000 diff --git a/data/50-system-asus.quirks b/data/50-system-asus.quirks new file mode 100644 index 00000000..ad120df5 --- /dev/null +++ b/data/50-system-asus.quirks @@ -0,0 +1,9 @@ +[Asus X555LAB] +MatchName=*ETPS/2 Elantech Touchpad* +MatchDMIModalias=dmi:*svnASUSTeKCOMPUTERINC.:pnX555LAB:* +ModelTouchpadVisibleMarker=1 + +[Asus UX21E] +MatchName=*ETPS/2 Elantech Touchpad* +MatchDMIModalias=dmi:*svnASUSTeKComputerInc.:pnUX21E:* +AttrPressureRange=24:10 diff --git a/data/50-system-chicony.quirks b/data/50-system-chicony.quirks new file mode 100644 index 00000000..808427f5 --- /dev/null +++ b/data/50-system-chicony.quirks @@ -0,0 +1,7 @@ +# Acer Hawaii Keyboard, uses Chicony VID +[Acer Hawaii Keyboard] +MatchUdevType=touchpad +MatchBus=usb +MatchVendor=0x4F2 +MatchProduct=0x1558 +AttrTPKComboLayout=below diff --git a/data/50-system-cyborg.quirks b/data/50-system-cyborg.quirks new file mode 100644 index 00000000..06137ae9 --- /dev/null +++ b/data/50-system-cyborg.quirks @@ -0,0 +1,6 @@ +[Saitek Cyborg RAT5] +MatchUdevType=mouse +MatchBus=usb +MatchVendor=0x06A3 +MatchProduct=0x0CD5 +ModelCyborgRat=1 diff --git a/data/50-system-dell.quirks b/data/50-system-dell.quirks new file mode 100644 index 00000000..3df31163 --- /dev/null +++ b/data/50-system-dell.quirks @@ -0,0 +1,15 @@ +[Dell Touchpads] +MatchName=* Touchpad +MatchDMIModalias=dmi:*svnDellInc.:* +ModelTouchpadVisibleMarker=1 + +[Dell Lattitude E6220] +MatchName=*AlpsPS/2 ALPS GlidePoint +MatchDMIModalias=dmi:*svnDellInc.:pnLatitudeE6220:* +AttrPressureRange=100:90 + +[Dell XPS L322X] +MatchName=*CyPS/2 Cypress Trackpad +MatchDMIModalias=dmi:*svnDell*:XPSL322X* +AttrPressureRange=32:20 +AttrPalmPressureThreshold=254 diff --git a/data/50-system-google.quirks b/data/50-system-google.quirks new file mode 100644 index 00000000..14f727d0 --- /dev/null +++ b/data/50-system-google.quirks @@ -0,0 +1,86 @@ +[Google Chromebook R13 CB5-312T] +MatchName=*Elan Touchpad* +MatchDeviceTree=*Chromebook R13 CB5-312T* +AttrPressureRange=6:4 + +[Google Chromebook CB5-312T] +MatchName=*Elan Touchpad* +MatchDeviceTree=*CB5-312T* +AttrPressureRange=6:4 + +[Google Chromebook Elm] +MatchName=*Elan Touchpad* +MatchDeviceTree=*Elm* +AttrPressureRange=6:4 + +[Google Chromebook Falco] +MatchUdevType=touchpad +MatchName=Cypress APA Trackpad ?cyapa? +MatchDMIModalias=dmi:*pn*Falco* +ModelChromebook=1 + +[Google Chromebook Mario] +MatchUdevType=touchpad +MatchName=SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*pn*Mario* +ModelChromebook=1 + +[Google Chromebook Butterfly] +MatchUdevType=touchpad +MatchName=Cypress APA Trackpad ?cyapa? +MatchDMIModalias=dmi:*pn*Butterfly* +ModelChromebook=1 + +[Google Chromebook Peppy] +MatchUdevType=touchpad +MatchName=Cypress APA Trackpad ?cyapa? +MatchDMIModalias=dmi:*pn*Peppy* +ModelChromebook=1 + +[Google Chromebook ZGB] +MatchUdevType=touchpad +MatchName=SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*pn*ZGB* +ModelChromebook=1 + +[Google Chromebook Parrot] +MatchUdevType=touchpad +MatchName=Cypress APA Trackpad ?cyapa? +MatchDMIModalias=dmi:*pn*Parrot* +ModelChromebook=1 + +[Google Chromebook Leon] +MatchUdevType=touchpad +MatchName=Cypress APA Trackpad ?cyapa? +MatchDMIModalias=dmi:*bvn*coreboot*:pn*Leon* +ModelChromebook=1 + +[Google Chromebook Wolf] +MatchUdevType=touchpad +MatchName=Cypress APA Trackpad ?cyapa? +MatchDMIModalias=dmi:*bvn*coreboot*:pn*Wolf* +ModelChromebook=1 + +[Google Chromebook Link] +MatchUdevType=touchpad +MatchName=Cypress APA Trackpad ?cyapa? +MatchDMIModalias=dmi:*svn*GOOGLE*:pn*Link* +ModelChromebook=1 + +[Google Chromebook Alex] +MatchUdevType=touchpad +MatchName=SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*pn*Alex* +ModelChromebook=1 + +[Google Chromebook Lumpy] +MatchUdevType=touchpad +MatchName=Cypress APA Trackpad ?cyapa? +MatchDMIModalias=dmi:*svn*SAMSUNG*:pn*Lumpy* +ModelChromebook=1 + +[Google Chromebook Samus] +MatchUdevType=touchpad +MatchName=Atmel maXTouch Touchpad +MatchDMIModalias=dmi:*svn*GOOGLE*:pn*Samus* +ModelChromebook=1 diff --git a/data/50-system-hp.quirks b/data/50-system-hp.quirks new file mode 100644 index 00000000..0d127dcd --- /dev/null +++ b/data/50-system-hp.quirks @@ -0,0 +1,24 @@ +[HP Compaq 6910p] +MatchName=*SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*svnHewlett-Packard:*pnHPCompaq6910p* +ModelHP6910Touchpad=1 + +[HP Compaq 8510w] +MatchName=*SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*svnHewlett-Packard:*pnHPCompaq8510w* +ModelHP8510Touchpad=1 + +[HP Pavillion dmi4] +MatchName=*SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*svnHewlett-Packard:*pnHPPaviliondm4NotebookPC* +ModelHPPavilionDM4Touchpad=1 + +[HP Stream 11] +MatchName=SYN1EDE:00 06CB:7442 +MatchDMIModalias=dmi:*svnHewlett-Packard:pnHPStreamNotebookPC11* +ModelHPStream11Touchpad=1 + +[HP ZBook Studio G3] +MatchName=AlpsPS/2 ALPS GlidePoint +MatchDMIModalias=dmi:*svnHP:pnHPZBookStudioG3:* +ModelHPZBookStudioG3=1 diff --git a/data/50-system-lenovo.quirks b/data/50-system-lenovo.quirks new file mode 100644 index 00000000..b365fc3c --- /dev/null +++ b/data/50-system-lenovo.quirks @@ -0,0 +1,78 @@ +[Lenovo Thinkpad Touchpad] +MatchName=*Synaptics* +MatchDMIModalias=dmi:*svnLENOVO:*:pvrThinkPad*:* +AttrThumbPressureThreshold=100 + +[Lenovo x230 Touchpad] +MatchName=*SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*svnLENOVO:*:pvrThinkPadX230* +ModelLenovoX230=1 + +[Lenovo T440p Touchpad PS/2] +MatchName=SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*svnLENOVO:*:pvrThinkPadT440p* +ModelLenovoT450Touchpad=1 + +[Lenovo T440p Touchpad RMI4] +MatchName=Synaptics tm2964-001 +MatchDMIModalias=dmi:*svnLENOVO:*:pvrThinkPadT440p* +ModelLenovoT450Touchpad=1 + +[Lenovo T440s Trackpoint] +MatchName=TPPS/2 IBM TrackPoint +MatchDMIModalias=dmi:*svnLENOVO:*:pvrThinkPadT440s* +AttrTrackpointRange=30 + +[Lenovo T440s Trackpoint] +MatchName=TPPS/2 IBM TrackPoint +MatchDMIModalias=dmi:*svnLENOVO:*:pvrThinkPadT450s* +AttrTrackpointRange=50 + +[Lenovo P50 Touchpad] +MatchName=SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*svnLENOVO:*:pvrThinkPadP50*: +ModelLenovoT450Touchpad=1 +AttrPalmPressureThreshold=150 + +[Lenovo *50 Touchpad] +MatchName=SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*svnLENOVO:*:pvrThinkPad??50*: +ModelLenovoT450Touchpad=1 +AttrPalmPressureThreshold=150 + +[Lenovo *60 Touchpad] +MatchName=SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*svnLENOVO:*:pvrThinkPad??60*: +ModelLenovoT450Touchpad=1 +AttrPalmPressureThreshold=150 + +[Lenovo X1 Carbon 3rd Touchpad] +MatchName=SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*svnLENOVO:*:pvrThinkPadX1Carbon3rd:* +ModelLenovoT450Touchpad=1 +AttrPalmPressureThreshold=150 + +[Lenovo ThinkPad Compact USB Keyboard with TrackPoint] +MatchUdevType=keyboard +MatchBus=usb +MatchVendor=0x17EF +MatchProduct=0x6047 +AttrKeyboardIntegration=external + +[Lenovo X280 Trackpoint] +MatchName=*ALPS TrackPoint* +MatchDMIModalias=dmi:*svnLENOVO:*:pvrThinkPadX280:* +AttrTrackpointRange=70 + +# Lenovo Thinkpad X1 Yoga disables the keyboard anyway but has the same device +# use a windows key on the screen and volume rocker on the side (#103749) +[Lenovo Thinkpad X1 Yoga] +MatchName=AT Translated Set 2 keyboard +MatchDMIModalias=dmi:*svnLENOVO:*pvrThinkPadX1Yoga1st:* +ModelTabletModeNoSuspend=1 + +# Lenovo Carbon X1 6th gen (RMI4 only, PS/2 is broken on this device) +[Lenovo Carbon X1 6th gen] +MatchName=Synaptics TM3288-010 +MatchDMIModalias=dmi:*svnLenovo:*pvrThinkPadX1Carbon6th:* +ModelLenovoCarbonX16th=1 diff --git a/data/50-system-system76.quirks b/data/50-system-system76.quirks new file mode 100644 index 00000000..15dd6108 --- /dev/null +++ b/data/50-system-system76.quirks @@ -0,0 +1,19 @@ +[System76 Bonobo Professional] +MatchName=SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*svnSystem76*pvrbonp5* +ModelSystem76Bonobo=1 + +[System76 Clevo] +MatchName=SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*pnW740SU*rnW740SU* +ModelClevoW740SU=1 + +[System76 Galago Ultra Pro] +MatchName=SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*svnSystem76*pvrgalu1* +ModelSystem76Galago=1 + +[System76 Kudu Professional] +MatchName=SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*svnSystem76*pvrkudp1* +ModelSystem76Kudu=1 diff --git a/data/README.md b/data/README.md new file mode 100644 index 00000000..66897938 --- /dev/null +++ b/data/README.md @@ -0,0 +1,78 @@ += libinput data file format = + +This directory contains hardware quirks used by libinput to work around bugs +in the hardware, device behavior and to supply information not obtained +through the kernel device. + +**THIS IS NOT STABLE API** + +The data format may change at any time. If your data file is not part of the +libinput git tree, do not expect it to work after an update. Absolutely no +guarantees are made for backwards-compatibility. + +**THIS IS NOT A CONFIGURATION API** + +Use the `libinput_device_config_foo()` functions for device configuration. +The quirks are hardware quirks only. + +== Data file naming == + +Data files are read in versionsort order, read order determines how values +override each other. A values read later override previously values. The +current structure is 10-generic-foo.quirks for generic settings, +30-vendor-foo.quirks for vendor-specific settings and 50-system-foo.quirks +for system vendors. This is not a fixed naming scheme and may change at any +time. It's an approximation only because some vendors are also system +vendors, e.g. Microsoft makes devices and laptops. + +Laptop-specific quirks should always go into the laptop vendor's file. + +== Sections, matches and values == + +A data file must contain at least one section, each section must have at +least one `Match` tag and at least one of either `Attr` or `Model`. Section +names are free-form and may contain spaces. + +``` +# This is a comment +[Some touchpad] +MatchBus=usb +# No quotes around strings +MatchName=*Synaptics Touchpad* +AttrSizeHint=50x50 +ModelSynapticsTouchpad=1 + +[Apple touchpad] +MatchVendor=0x5AC +MatchProduct=0x123 +ModelAppleTouchpad=1 +``` + +Comments are lines starting with `#`. + +All `Model` tags take a value of either `1` or `0`. + +All `Attr` tag values are specific to that attribute. + +== Parser errors == + +The following will cause parser errors and are considered invalid data +files: + +* Whitespace at the beginning of the line +* Inline comments, e.g. `MatchBus=usb # oops, fail` +* Sections without at least one `Match*` entry +* Sections with the same `Match*` entry repeated +* Sections without at least one of `Model*` or `Attr` entries +* A `Model` tag with a value other than `1` or `0` +* A string property with enclosing quotes + +== Debugging == + +When modifying a data file, use the `libinput list-quirks` tool to +verify the changes. The tool can be pointed at the data directory to +analyse, use `--verbose` to get more info. For example: + +``` +libinput list-quirks --data-dir /path/to/git/repo/data/ --verbose /dev/input/event0 +``` diff --git a/doc/device-quirks.dox b/doc/device-quirks.dox new file mode 100644 index 00000000..35e78215 --- /dev/null +++ b/doc/device-quirks.dox @@ -0,0 +1,102 @@ +/** +@page device-quirks Device quirks + +libinput requires extra information from devices that is not always readily +available. For example, some touchpads are known to have jumping cursors +under specific conditions. libinput ships a set of files containting the +so-called model quirks to provide that information. Model quirks are usually +installed under `/usr/share/libinput/.quirks` and are standard +`.ini` files. A file may contain multiple section headers (`[some +identifier]`) followed by one or more `MatchFoo=Bar` directives, followed by +at least one of `ModelFoo=1` or `AttrFoo=bar` directive. See the +`data/README.md` file in the libinput source repository for more details on +their contents. + +@note Model quirks are internal API and may change at any time. No +backwards-compatibility is guaranteed. + +For example, a quirks file may have this content to label all keyboards on +the serial bus (PS/2) as internal keyboards: + +@verbatim +[Serial Keyboards] +MatchUdevType=keyboard +MatchBus=serial +AttrKeyboardIntegration=internal +@endverbatim + +The model quirks are part of the source distribution and should never be +modified locally. Updates to libinput may overwrite modifications or even +stop parsing any property. For temporary local workarounds, see @ref +device-quirks-local. + +Device quirks are parsed on libinput initialization. A parsing error in the +device quirks disables **all** device quirks and may negatively impact +device behavior on the host. If the quirks cannot be loaded, an error +message is posted to the log and users should use the information in @ref +device-quirks-debugging to verify their quirks files. + +@section device-quirks-local Installing temporary local device quirks + +The model quirks are part of the source distribution and should never be +modified. For temporary local workarounds, libinput reads the +`/etc/libinput/local-overrides.quirks` file. Users may add a sections to +this file to add a device quirk for a local device but beware that **any +modification must be upstreamed** or it may cease to work at any time. + +@note Model quirks are internal API and may change at any time. No +backwards-compatibility is guaranteed. Local overrides should only be used +until the distribution updates the libinput packages. + +The `local-overrides.quirks` file usually needs to be created by the user. +Once the required section has been added, use the information from section +@ref device-quirks-debugging to validate and test the quirks. + +@section device-quirks-debugging Debugging device quirks + +libinput provides the `libinput list-quirks` tool to list and debug model +quirks that apply to one or more local devices. + +@verbatim +$ libinput list-quirks /dev/input/event19 +Device has no quirks defined +$ libinput list-quirks /dev/input/event0 +AttrLidSwitchReliability +@endverbatim + +When called with the `--verbose` argument, `libinput list-quirks` prints +information about all files and its attempts to match the device: + +@verbatim +$ libinput list-quirks --verbose /dev/input/event0 +quirks debug: /usr/share/share/libinput is data root +quirks debug: /usr/share/share/libinput/10-generic-keyboard.quirks +quirks debug: /usr/share/share/libinput/10-generic-lid.quirks +[...] +quirks debug: /usr/share/etc/libinput/local-overrides.quirks +quirks debug: /dev/input/event0: fetching quirks +quirks debug: [Serial Keyboards] (10-generic-keyboard.quirks) wants MatchBus but we don't have that +quirks debug: [Lid Switch Ct9] (10-generic-lid.quirks) matches for MatchName +quirks debug: [Lid Switch Ct10] (10-generic-lid.quirks) matches for MatchName +quirks debug: [Lid Switch Ct10] (10-generic-lid.quirks) matches for MatchDMIModalias +quirks debug: [Lid Switch Ct10] (10-generic-lid.quirks) is full match +quirks debug: property added: AttrLidSwitchReliability from [Lid Switch Ct10] (10-generic-lid.quirks) +quirks debug: [Aiptek No Tilt Tablet] (30-vendor-aiptek.quirks) wants MatchBus but we don't have that +[...] +quirks debug: [HUION PenTablet] (30-vendor-huion.quirks) wants MatchBus but we don't have that +quirks debug: [Logitech Marble Mouse Trackball] (30-vendor-logitech.quirks) wants MatchBus but we don't have that +quirks debug: [Logitech K400] (30-vendor-logitech.quirks) wants MatchBus but we don't have that +quirks debug: [Logitech K400r] (30-vendor-logitech.quirks) wants MatchBus but we don't have that +quirks debug: [Logitech K830] (30-vendor-logitech.quirks) wants MatchBus but we don't have that +quirks debug: [Logitech K400Plus] (30-vendor-logitech.quirks) wants MatchBus but we don't have that +quirks debug: [Logitech Wireless Touchpad] (30-vendor-logitech.quirks) wants MatchBus but we don't have that +quirks debug: [Microsoft Surface 3 Lid Switch] (30-vendor-microsoft.quirks) matches for MatchName +[...] +AttrLidSwitchReliability +@endverbatim + +Note that this is an example only, the output may change over time. The tool +uses the same parser as libinput and any parsing errors will show up in the +output. + +*/ diff --git a/doc/page-hierarchy.dox b/doc/page-hierarchy.dox index 6e164f53..834b2f4b 100644 --- a/doc/page-hierarchy.dox +++ b/doc/page-hierarchy.dox @@ -37,6 +37,7 @@ @page general General +- @subpage device-quirks - @subpage udev_config - @subpage seats - @subpage timestamps diff --git a/meson.build b/meson.build index 69a386c7..b8735ced 100644 --- a/meson.build +++ b/meson.build @@ -181,6 +181,54 @@ libfilter = static_library('filter', src_libfilter, include_directories : includes_include) dep_libfilter = declare_dependency(link_with : libfilter) +############ libquirks.a ############# +libinput_data_path = join_paths(get_option('prefix'), get_option('datadir'), 'libinput') +libinput_data_override_path = join_paths(get_option('prefix'), + get_option('sysconfdir'), + 'libinput', + 'local-overrides.quirks') +config_h.set_quoted('LIBINPUT_DATA_DIR', libinput_data_path) +config_h.set_quoted('LIBINPUT_DATA_OVERRIDE_FILE', libinput_data_override_path) + +quirks_data = [ + 'data/10-generic-keyboard.quirks', + 'data/10-generic-lid.quirks', + 'data/10-generic-trackball.quirks', + 'data/30-vendor-aiptek.quirks', + 'data/30-vendor-alps.quirks', + 'data/30-vendor-cyapa.quirks', + 'data/30-vendor-elantech.quirks', + 'data/30-vendor-huion.quirks', + 'data/30-vendor-ibm.quirks', + 'data/30-vendor-logitech.quirks', + 'data/30-vendor-microsoft.quirks', + 'data/30-vendor-razer.quirks', + 'data/30-vendor-synaptics.quirks', + 'data/30-vendor-wacom.quirks', + 'data/50-system-apple.quirks', + 'data/50-system-asus.quirks', + 'data/50-system-chicony.quirks', + 'data/50-system-cyborg.quirks', + 'data/50-system-dell.quirks', + 'data/50-system-google.quirks', + 'data/50-system-hp.quirks', + 'data/50-system-lenovo.quirks', + 'data/50-system-system76.quirks', +] + +install_data(quirks_data, install_dir : libinput_data_path) + +src_libquirks = [ + 'src/quirks.c', + 'src/quirks.h', +] + +deps_libquirks = [dep_udev, dep_libinput_util] +libquirks = static_library('quirks', src_libquirks, + dependencies : deps_libquirks, + include_directories : includes_include) +dep_libquirks = declare_dependency(link_with : libquirks) + ############ libinput.so ############ install_headers('src/libinput.h') src_libinput = src_libfilter + [ @@ -220,7 +268,8 @@ deps_libinput = [ dep_lm, dep_rt, dep_libwacom, - dep_libinput_util + dep_libinput_util, + dep_libquirks ] libinput_version_h_config = configuration_data() @@ -317,6 +366,7 @@ if get_option('documentation') meson.source_root() + '/doc/clickpad-softbuttons.dox', meson.source_root() + '/doc/contributing.dox', meson.source_root() + '/doc/device-configuration-via-udev.dox', + meson.source_root() + '/doc/device-quirks.dox', meson.source_root() + '/doc/faqs.dox', meson.source_root() + '/doc/gestures.dox', meson.source_root() + '/doc/middle-button-emulation.dox', @@ -435,6 +485,22 @@ configure_file(input : 'tools/libinput-debug-events.man', install_dir : join_paths(get_option('mandir'), 'man1') ) +libinput_list_quirks_sources = [ 'tools/libinput-list-quirks.c' ] +executable('libinput-list-quirks', + libinput_list_quirks_sources, + dependencies : [dep_libquirks, dep_libinput], + include_directories : [includes_src, includes_include], + install_dir : libinput_tool_path, + install : true + ) + +configure_file(input : 'tools/libinput-list-quirks.man', + output : 'libinput-list-quirks.1', + configuration : man_config, + install : true, + install_dir : join_paths(get_option('mandir'), 'man1') + ) + libinput_list_devices_sources = [ 'tools/libinput-list-devices.c' ] executable('libinput-list-devices', libinput_list_devices_sources, @@ -697,6 +763,7 @@ if get_option('tests') dep_dl, dep_lm, dep_libsystemd, + dep_libquirks, ] configure_file(input : 'udev/80-libinput-test-device.rules', @@ -763,7 +830,8 @@ if get_option('tests') 'test/test-keyboard.c', 'test/test-device.c', 'test/test-gestures.c', - 'test/test-switch.c' + 'test/test-switch.c', + 'test/test-quirks.c', ] def_LT_VERSION = '-DLIBINPUT_LT_VERSION="@0@:@1@:@2@"'.format(libinput_lt_c, libinput_lt_r, libinput_lt_a) libinput_test_runner = executable('libinput-test-suite-runner', diff --git a/src/quirks.c b/src/quirks.c new file mode 100644 index 00000000..7cef8a79 --- /dev/null +++ b/src/quirks.c @@ -0,0 +1,1437 @@ +/* + * Copyright © 2018 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +/* This has the hallmarks of a library to make it re-usable from the tests + * and from the list-quirks tool. It doesn't have all of the features from a + * library you'd expect though + */ + +#undef NDEBUG /* You don't get to disable asserts here */ +#include +#include +#include +#include +#include + +#include "libinput-util.h" +#include "libinput-private.h" + +#include "quirks.h" + +/* Custom logging so we can have detailed output for the tool but minimal + * logging for libinput itself. */ +#define qlog_debug(ctx_, ...) quirk_log_msg((ctx_), QLOG_NOISE, __VA_ARGS__) +#define qlog_info(ctx_, ...) quirk_log_msg((ctx_), QLOG_INFO, __VA_ARGS__) +#define qlog_error(ctx_, ...) quirk_log_msg((ctx_), QLOG_ERROR, __VA_ARGS__) +#define qlog_parser(ctx_, ...) quirk_log_msg((ctx_), QLOG_PARSER_ERROR, __VA_ARGS__) + +enum property_type { + PT_UINT, + PT_INT, + PT_STRING, + PT_BOOL, + PT_DIMENSION, + PT_RANGE, +}; + +/** + * Generic value holder for the property types we support. The type + * identifies which value in the union is defined and we expect callers to + * already know which type yields which value. + */ +struct property { + size_t refcount; + struct list link; /* struct sections.properties */ + + enum quirk id; + enum property_type type; + union { + bool b; + uint32_t u; + int32_t i; + char *s; + struct quirk_dimensions dim; + struct quirk_range range; + } value; +}; + +enum match_flags { + M_NAME = (1 << 0), + M_BUS = (1 << 1), + M_VID = (1 << 2), + M_PID = (1 << 3), + M_DMI = (1 << 4), + M_UDEV_TYPE = (1 << 5), + M_DT = (1 << 6), + + M_LAST = M_DT, +}; + +enum bustype { + BT_UNKNOWN, + BT_USB, + BT_BLUETOOTH, + BT_PS2, + BT_RMI, + BT_I2C, +}; + +enum udev_type { + UDEV_MOUSE = (1 << 1), + UDEV_POINTINGSTICK = (1 << 2), + UDEV_TOUCHPAD = (1 << 3), + UDEV_TABLET = (1 << 4), + UDEV_TABLET_PAD = (1 << 5), + UDEV_JOYSTICK = (1 << 6), + UDEV_KEYBOARD = (1 << 7), +}; + +/** + * Contains the combined set of matches for one section or the values for + * one device. + * + * bits defines which fields are set, the rest is zero. + */ +struct match { + uint32_t bits; + + char *name; + enum bustype bus; + uint32_t vendor; + uint32_t product; + + char *dmi; + + /* We can have more than one type set, so this is a bitfield */ + uint32_t udev_type; + + char *dt; /* FIXME: clarify */ +}; + +/** + * Represents one section in the .quirks file. + */ +struct section { + struct list link; + + bool has_match; /* to check for empty sections */ + bool has_property; /* to check for empty sections */ + + char *name; /* the [Section Name] */ + struct match match; + struct list properties; +}; + +/** + * The struct returned to the caller. It contains the + * properties for a given device. + */ +struct quirks { + size_t refcount; + struct list link; /* struct quirks_context.quirks */ + + /* These are not ref'd, just a collection of pointers */ + struct property **properties; + size_t nproperties; +}; + +/** + * Quirk matching context, initialized once with quirks_init_subsystem() + */ +struct quirks_context { + size_t refcount; + + libinput_log_handler log_handler; + enum quirks_log_type log_type; + struct libinput *libinput; /* for logging */ + + char *dmi; + + struct list sections; + + /* list of quirks handed to libinput, just for bookkeeping */ + struct list quirks; +}; + +LIBINPUT_ATTRIBUTE_PRINTF(3, 0) +static inline void +quirk_log_msg_va(struct quirks_context *ctx, + enum quirks_log_priorities priority, + const char *format, + va_list args) +{ + enum libinput_log_priority p = priority; + + switch (priority) { + /* We don't use this if we're logging through libinput */ + default: + case QLOG_NOISE: + case QLOG_PARSER_ERROR: + if (ctx->log_type == QLOG_LIBINPUT_LOGGING) + return; + break; + case QLOG_DEBUG: /* These map straight to libinput priorities */ + case QLOG_INFO: + case QLOG_ERROR: + break; + } + + ctx->log_handler(ctx->libinput, p, format, args); +} + +LIBINPUT_ATTRIBUTE_PRINTF(3, 4) +static inline void +quirk_log_msg(struct quirks_context *ctx, + enum quirks_log_priorities priority, + const char *format, + ...) +{ + va_list args; + + va_start(args, format); + quirk_log_msg_va(ctx, priority, format, args); + va_end(args); + +} + +const char * +quirk_get_name(enum quirk q) +{ + switch(q) { + case QUIRK_MODEL_ALPS_TOUCHPAD: return "ModelALPSTouchpad"; + case QUIRK_MODEL_APPLE_TOUCHPAD: return "ModelAppleTouchpad"; + case QUIRK_MODEL_APPLE_MAGICMOUSE: return "ModelAppleMagicMouse"; + case QUIRK_MODEL_TABLET_NO_TILT: return "ModelTabletNoTilt"; + case QUIRK_MODEL_APPLE_TOUCHPAD_ONEBUTTON: return "ModelAppleTouchpadOneButton"; + case QUIRK_MODEL_TOUCHPAD_VISIBLE_MARKER: return "ModelTouchpadVisibleMarker"; + case QUIRK_MODEL_CYBORG_RAT: return "ModelCyborgRat"; + case QUIRK_MODEL_CHROMEBOOK: return "ModelChromebook"; + case QUIRK_MODEL_HP6910_TOUCHPAD: return "ModelHP6910Touchpad"; + case QUIRK_MODEL_HP8510_TOUCHPAD: return "ModelHP8510Touchpad"; + case QUIRK_MODEL_HP_PAVILION_DM4_TOUCHPAD: return "ModelHPPavilionDM4Touchpad"; + case QUIRK_MODEL_HP_STREAM11_TOUCHPAD: return "ModelHPStream11Touchpad"; + case QUIRK_MODEL_HP_ZBOOK_STUDIO_G3: return "ModelHPZBookStudioG3"; + case QUIRK_MODEL_TABLET_NO_PROXIMITY_OUT: return "ModelTabletNoProximityOut"; + case QUIRK_MODEL_LENOVO_SCROLLPOINT: return "ModelLenovoScrollPoint"; + case QUIRK_MODEL_LENOVO_X230: return "ModelLenovoX230"; + case QUIRK_MODEL_LENOVO_T450_TOUCHPAD: return "ModelLenovoT450Touchpad"; + case QUIRK_MODEL_TABLET_MODE_NO_SUSPEND: return "ModelTabletModeNoSuspend"; + case QUIRK_MODEL_LENOVO_CARBON_X1_6TH: return "ModelLenovoCarbonX16th"; + case QUIRK_MODEL_TRACKBALL: return "ModelTrackball"; + case QUIRK_MODEL_LOGITECH_MARBLE_MOUSE: return "ModelLogitechMarbleMouse"; + case QUIRK_MODEL_BOUNCING_KEYS: return "ModelBouncingKeys"; + case QUIRK_MODEL_SYNAPTICS_SERIAL_TOUCHPAD: return "ModelSynapticsSerialTouchpad"; + case QUIRK_MODEL_SYSTEM76_BONOBO: return "ModelSystem76Bonobo"; + case QUIRK_MODEL_CLEVO_W740SU: return "ModelClevoW740SU"; + case QUIRK_MODEL_SYSTEM76_GALAGO: return "ModelSystem76Galago"; + case QUIRK_MODEL_SYSTEM76_KUDU: return "ModelSystem76Kudu"; + case QUIRK_MODEL_WACOM_TOUCHPAD: return "ModelWacomTouchpad"; + case QUIRK_MODEL_JUMPING_SEMI_MT: return "ModelJumpingSemiMT"; + + case QUIRK_ATTR_SIZE_HINT: return "AttrSizeHint"; + case QUIRK_ATTR_TOUCH_SIZE_RANGE: return "AttrTouchSizeRange"; + case QUIRK_ATTR_PALM_SIZE_THRESHOLD: return "AttrPalmSizeThreshold"; + case QUIRK_ATTR_LID_SWITCH_RELIABILITY: return "AttrLidSwitchReliability"; + case QUIRK_ATTR_KEYBOARD_INTEGRATION: return "AttrKeyboardIntegration"; + case QUIRK_ATTR_TPKBCOMBO_LAYOUT: return "AttrTPKComboLayout"; + case QUIRK_ATTR_PRESSURE_RANGE: return "AttrPressureRange"; + case QUIRK_ATTR_PALM_PRESSURE_THRESHOLD: return "AttrPalmPressureThreshold"; + case QUIRK_ATTR_RESOLUTION_HINT: return "AttrResolutionHint"; + case QUIRK_ATTR_TRACKPOINT_RANGE: return "AttrTrackpointRange"; + case QUIRK_ATTR_THUMB_PRESSURE_THRESHOLD: return "AttrThumbPressureThreshold"; + default: + abort(); + } +} + +static inline const char * +matchflagname(enum match_flags f) +{ + switch(f) { + case M_NAME: return "MatchName"; break; + case M_BUS: return "MatchBus"; break; + case M_VID: return "MatchVendor"; break; + case M_PID: return "MatchProduct"; break; + case M_DMI: return "MatchDMIModalias"; break; + case M_UDEV_TYPE: return "MatchUdevType"; break; + case M_DT: return "MatchDeviceTree"; break; + default: + abort(); + } +}; + +static inline struct property * +property_new(void) +{ + struct property *p; + + p = zalloc(sizeof *p); + p->refcount = 1; + list_init(&p->link); + + return p; +} + +static inline struct property * +property_ref(struct property *p) +{ + assert(p->refcount > 0); + p->refcount++; + return p; +}; + +static inline struct property * +property_unref(struct property *p) +{ + /* Note: we don't cleanup here, that is a separate call so we + can abort if we haven't cleaned up correctly. */ + assert(p->refcount > 0); + p->refcount--; + + return NULL; +}; + +/* Separate call so we can verify that the caller unrefs the property + * before shutting down the subsystem. + */ +static inline void +property_cleanup(struct property *p) +{ + /* If we get here, the quirks must've been removed already */ + property_unref(p); + assert(p->refcount == 0); + + list_remove(&p->link); + if (p->type == PT_STRING) + free(p->value.s); + free(p); +} + +/** + * Return the dmi modalias from the udev device. + */ +static inline char * +init_dmi(void) +{ + struct udev *udev; + struct udev_device *udev_device; + const char *modalias; + char *copy = NULL; + const char *syspath = "/sys/devices/virtual/dmi/id"; + + udev = udev_new(); + if (!udev) + return NULL; + + udev_device = udev_device_new_from_syspath(udev, syspath); + if (udev_device) + modalias = udev_device_get_property_value(udev_device, + "MODALIAS"); + + /* Not sure whether this could ever really fail, if so we should + * open the sysfs file directly. But then udev wouldn't have failed, + * so... */ + if (!modalias) + modalias = "dmi:*"; + + copy = safe_strdup(modalias); + + udev_device_unref(udev_device); + udev_unref(udev); + + return copy; +} + +static inline struct section * +section_new(const char *path, const char *name) +{ + struct section *s = zalloc(sizeof(*s)); + + xasprintf(&s->name, "%s (%s)", name, basename(path)); + list_init(&s->link); + list_init(&s->properties); + + return s; +} + +static inline void +section_destroy(struct section *s) +{ + struct property *p, *tmp; + + free(s->name); + free(s->match.name); + free(s->match.dmi); + free(s->match.dt); + + list_for_each_safe(p, tmp, &s->properties, link) + property_cleanup(p); + + assert(list_empty(&s->properties)); + + list_remove(&s->link); + free(s); +} + +/** + * Parse a MatchFooBar=banana line. + * + * @param section The section struct to be filled in + * @param key The MatchFooBar part of the line + * @param value The banana part of the line. + * + * @return true on success, false otherwise. + */ +static bool +parse_match(struct quirks_context *ctx, + struct section *s, + const char *key, + const char *value) +{ + int rc = false; + +#define check_set_bit(s_, bit_) { \ + if ((s_)->match.bits & (bit_)) goto out; \ + (s_)->match.bits |= (bit_); \ + } + + assert(strlen(value) >= 1); + + if (streq(key, "MatchName")) { + check_set_bit(s, M_NAME); + s->match.name = safe_strdup(value); + } else if (streq(key, "MatchBus")) { + check_set_bit(s, M_BUS); + if (streq(value, "usb")) + s->match.bus = BT_USB; + else if (streq(value, "bluetooth")) + s->match.bus = BT_BLUETOOTH; + else if (streq(value, "ps2")) + s->match.bus = BT_PS2; + else if (streq(value, "rmi")) + s->match.bus = BT_RMI; + else if (streq(value, "i2c")) + s->match.bus = BT_I2C; + else + goto out; + } else if (streq(key, "MatchVendor")) { + unsigned int vendor; + + check_set_bit(s, M_VID); + if (!strneq(value, "0x", 2) || + !safe_atou_base(value, &vendor, 16) || + vendor > 0xFFFF) + goto out; + + s->match.vendor = vendor; + } else if (streq(key, "MatchProduct")) { + unsigned int product; + + check_set_bit(s, M_PID); + if (!strneq(value, "0x", 2) || + !safe_atou_base(value, &product, 16) || + product > 0xFFFF) + goto out; + + s->match.product = product; + } else if (streq(key, "MatchDMIModalias")) { + check_set_bit(s, M_DMI); + if (!strneq(value, "dmi:", 4)) { + qlog_parser(ctx, + "%s: MatchDMIModalias must start with 'dmi:'\n", + s->name); + goto out; + } + s->match.dmi = safe_strdup(value); + } else if (streq(key, "MatchUdevType")) { + check_set_bit(s, M_UDEV_TYPE); + if (streq(value, "touchpad")) + s->match.udev_type = UDEV_TOUCHPAD; + else if (streq(value, "mouse")) + s->match.udev_type = UDEV_MOUSE; + else if (streq(value, "pointingstick")) + s->match.udev_type = UDEV_POINTINGSTICK; + else if (streq(value, "keyboard")) + s->match.udev_type = UDEV_KEYBOARD; + else if (streq(value, "joystick")) + s->match.udev_type = UDEV_JOYSTICK; + else if (streq(value, "tablet")) + s->match.udev_type = UDEV_TABLET; + else if (streq(value, "tablet-pad")) + s->match.udev_type = UDEV_TABLET_PAD; + else + goto out; + } else if (streq(key, "MatchDeviceTree")) { + /* FIXME */ + } else { + qlog_error(ctx, "Unknown match key '%s'\n", key); + goto out; + } + +#undef check_set_bit + s->has_match = true; + rc = true; +out: + return rc; +} + +/** + * Parse a ModelFooBar=1 line. + * + * @param section The section struct to be filled in + * @param key The ModelFooBar part of the line + * @param value The value after the =, must be 1 or 0. + * + * @return true on success, false otherwise. + */ +static bool +parse_model(struct quirks_context *ctx, + struct section *s, + const char *key, + const char *value) +{ + enum quirk quirks[] = { + QUIRK_MODEL_ALPS_TOUCHPAD, + QUIRK_MODEL_APPLE_TOUCHPAD, + QUIRK_MODEL_APPLE_MAGICMOUSE, + QUIRK_MODEL_TABLET_NO_TILT, + QUIRK_MODEL_APPLE_TOUCHPAD_ONEBUTTON, + QUIRK_MODEL_TOUCHPAD_VISIBLE_MARKER, + QUIRK_MODEL_CYBORG_RAT, + QUIRK_MODEL_CHROMEBOOK, + QUIRK_MODEL_HP6910_TOUCHPAD, + QUIRK_MODEL_HP8510_TOUCHPAD, + QUIRK_MODEL_HP_PAVILION_DM4_TOUCHPAD, + QUIRK_MODEL_HP_STREAM11_TOUCHPAD, + QUIRK_MODEL_HP_ZBOOK_STUDIO_G3, + QUIRK_MODEL_TABLET_NO_PROXIMITY_OUT, + QUIRK_MODEL_LENOVO_SCROLLPOINT, + QUIRK_MODEL_LENOVO_X230, + QUIRK_MODEL_LENOVO_T450_TOUCHPAD, + QUIRK_MODEL_TABLET_MODE_NO_SUSPEND, + QUIRK_MODEL_LENOVO_CARBON_X1_6TH, + QUIRK_MODEL_TRACKBALL, + QUIRK_MODEL_LOGITECH_MARBLE_MOUSE, + QUIRK_MODEL_BOUNCING_KEYS, + QUIRK_MODEL_SYNAPTICS_SERIAL_TOUCHPAD, + QUIRK_MODEL_SYSTEM76_BONOBO, + QUIRK_MODEL_CLEVO_W740SU, + QUIRK_MODEL_SYSTEM76_GALAGO, + QUIRK_MODEL_SYSTEM76_KUDU, + QUIRK_MODEL_WACOM_TOUCHPAD, + QUIRK_MODEL_JUMPING_SEMI_MT, + }; + bool b; + enum quirk *q; + + assert(strneq(key, "Model", 5)); + + if (streq(value, "1")) + b = true; + else if (streq(value, "0")) + b = false; + else + return false; + + ARRAY_FOR_EACH(quirks, q) { + if (streq(key, quirk_get_name(*q))) { + struct property *p = property_new(); + p->id = *q, + p->type = PT_BOOL; + p->value.b = b; + list_append(&s->properties, &p->link); + s->has_property = true; + return true; + } + } + + qlog_error(ctx, "Unknown key %s in %s\n", key, s->name); + + return false; +} + +/** + * Parse a AttrFooBar=banana line. + * + * @param section The section struct to be filled in + * @param key The AttrFooBar part of the line + * @param value The banana part of the line. + * + * Value parsing depends on the attribute type. + * + * @return true on success, false otherwise. + */ +static inline bool +parse_attr(struct quirks_context *ctx, + struct section *s, + const char *key, + const char *value) +{ + struct property *p = property_new(); + bool rc = false; + struct quirk_dimensions dim; + struct quirk_range range; + unsigned int v; + + if (streq(key, quirk_get_name(QUIRK_ATTR_SIZE_HINT))) { + p->id = QUIRK_ATTR_SIZE_HINT; + if (!parse_dimension_property(value, &dim.x, &dim.y)) + goto out; + p->type = PT_DIMENSION; + p->value.dim = dim; + rc = true; + } else if (streq(key, quirk_get_name(QUIRK_ATTR_TOUCH_SIZE_RANGE))) { + p->id = QUIRK_ATTR_TOUCH_SIZE_RANGE; + if (!parse_range_property(value, &range.upper, &range.lower)) + goto out; + p->type = PT_RANGE; + p->value.range = range; + rc = true; + } else if (streq(key, quirk_get_name(QUIRK_ATTR_PALM_SIZE_THRESHOLD))) { + p->id = QUIRK_ATTR_PALM_SIZE_THRESHOLD; + if (!safe_atou(value, &v)) + goto out; + p->type = PT_UINT; + p->value.u = v; + rc = true; + } else if (streq(key, quirk_get_name(QUIRK_ATTR_LID_SWITCH_RELIABILITY))) { + p->id = QUIRK_ATTR_LID_SWITCH_RELIABILITY; + if (!streq(value, "reliable") && + !streq(value, "write_open")) + goto out; + p->type = PT_STRING; + p->value.s = safe_strdup(value); + rc = true; + } else if (streq(key, quirk_get_name(QUIRK_ATTR_KEYBOARD_INTEGRATION))) { + p->id = QUIRK_ATTR_KEYBOARD_INTEGRATION; + if (!streq(value, "internal") && !streq(value, "external")) + goto out; + p->type = PT_STRING; + p->value.s = safe_strdup(value); + rc = true; + } else if (streq(key, quirk_get_name(QUIRK_ATTR_TPKBCOMBO_LAYOUT))) { + p->id = QUIRK_ATTR_TPKBCOMBO_LAYOUT; + if (!streq(value, "below")) + goto out; + p->type = PT_STRING; + p->value.s = safe_strdup(value); + rc = true; + } else if (streq(key, quirk_get_name(QUIRK_ATTR_PRESSURE_RANGE))) { + p->id = QUIRK_ATTR_PRESSURE_RANGE; + if (!parse_range_property(value, &range.upper, &range.lower)) + goto out; + p->type = PT_RANGE; + p->value.range = range; + rc = true; + } else if (streq(key, quirk_get_name(QUIRK_ATTR_PALM_PRESSURE_THRESHOLD))) { + p->id = QUIRK_ATTR_PALM_PRESSURE_THRESHOLD; + if (!safe_atou(value, &v)) + goto out; + p->type = PT_UINT; + p->value.u = v; + rc = true; + } else if (streq(key, quirk_get_name(QUIRK_ATTR_RESOLUTION_HINT))) { + p->id = QUIRK_ATTR_RESOLUTION_HINT; + if (!parse_dimension_property(value, &dim.x, &dim.y)) + goto out; + p->type = PT_DIMENSION; + p->value.dim = dim; + rc = true; + } else if (streq(key, quirk_get_name(QUIRK_ATTR_TRACKPOINT_RANGE))) { + p->id = QUIRK_ATTR_TRACKPOINT_RANGE; + if (!safe_atou(value, &v)) + goto out; + p->type = PT_UINT; + p->value.u = v; + rc = true; + } else if (streq(key, quirk_get_name(QUIRK_ATTR_THUMB_PRESSURE_THRESHOLD))) { + p->id = QUIRK_ATTR_THUMB_PRESSURE_THRESHOLD; + if (!safe_atou(value, &v)) + goto out; + p->type = PT_UINT; + p->value.u = v; + rc = true; + } else { + qlog_error(ctx, "Unknown key %s in %s\n", key, s->name); + } +out: + if (rc) { + list_append(&s->properties, &p->link); + s->has_property = true; + } else { + property_cleanup(p); + } + return rc; +} + +/** + * Parse a single line, expected to be in the format Key=value. Anything + * else will be rejected with a failure. + * + * Our data files can only have Match, Model and Attr, so let's check for + * those too. + */ +static bool +parse_value_line(struct quirks_context *ctx, struct section *s, const char *line) +{ + char **strv; + const char *key, *value; + bool rc = false; + + strv = strv_from_string(line, "="); + if (strv[0] == NULL || strv[1] == NULL || strv[2] != NULL) { + goto out; + } + + + key = strv[0]; + value = strv[1]; + if (strlen(key) == 0 || strlen(value) == 0) + goto out; + + /* Whatever the value is, it's not supposed to be in quotes */ + if (value[0] == '"') + goto out; + + if (strneq(key, "Match", 5)) + rc = parse_match(ctx, s, key, value); + else if (strneq(key, "Model", 5)) + rc = parse_model(ctx, s, key, value); + else if (strneq(key, "Attr", 4)) + rc = parse_attr(ctx, s, key, value); + else + qlog_error(ctx, "Unknown value prefix %s\n", line); +out: + strv_free(strv); + return rc; +} + +static inline bool +parse_file(struct quirks_context *ctx, const char *path) +{ + enum state { + STATE_SECTION, + STATE_MATCH, + STATE_MATCH_OR_VALUE, + STATE_VALUE_OR_SECTION, + STATE_ANY, + }; + FILE *fp; + char line[512]; + bool rc = false; + enum state state = STATE_SECTION; + struct section *section = NULL; + int lineno = -1; + + qlog_debug(ctx, "%s\n", path); + + /* Not using open_restricted here, if we can't access + * our own data files, our installation is screwed up. + */ + fp = fopen(path, "r"); + if (!fp) { + /* If the file doesn't exist that's fine. Only way this can + * happen is for the custom override file, all others are + * provided by scandir so they do exist. Short of races we + * don't care about. */ + if (errno == ENOENT) + return true; + + qlog_error(ctx, "%s: failed to open file\n", path); + goto out; + } + + while (fgets(line, sizeof(line), fp)) { + lineno++; + if (strlen(line) >= 1 && line[strlen(line) - 1] == '\n') + line[strlen(line) - 1] = '\0'; /* drop trailing \n */ + + if (strlen(line) == 0) + continue; + + /* We don't use quotes for strings, so we really don't want + * erroneous trailing whitespaces */ + switch (line[strlen(line) - 1]) { + case ' ': + case '\t': + qlog_parser(ctx, + "%s:%d: Trailing whitespace '%s'\n", + path, lineno, line); + goto out; + } + + switch (line[0]) { + case '\0': + case '\n': + case '#': + break; + /* white space not allowed */ + case ' ': + case '\t': + qlog_parser(ctx, "%s:%d: Preceding whitespace '%s'\n", + path, lineno, line); + goto out; + /* section title */ + case '[': + if (line[strlen(line) - 1] != ']') { + qlog_parser(ctx, "%s:%d: Closing ] missing '%s'\n", + path, lineno, line); + goto out; + } + + if (state != STATE_SECTION && + state != STATE_VALUE_OR_SECTION) { + qlog_parser(ctx, "%s:%d: expected section before %s\n", + path, lineno, line); + goto out; + } + if (section && + (!section->has_match || !section->has_property)) { + qlog_parser(ctx, "%s:%d: previous section %s was empty\n", + path, lineno, section->name); + goto out; /* Previous section was empty */ + } + + state = STATE_MATCH; + section = section_new(path, line); + list_append(&ctx->sections, §ion->link); + break; + /* entries must start with A-Z */ + case 'A'...'Z': + switch (state) { + case STATE_SECTION: + qlog_parser(ctx, "%s:%d: expected [Section], got %s\n", + path, lineno, line); + goto out; + case STATE_MATCH: + if (!strneq(line, "Match", 5)) { + qlog_parser(ctx, "%s:%d: expected MatchFoo=bar, have %s\n", + path, lineno, line); + goto out; + } + state = STATE_MATCH_OR_VALUE; + break; + case STATE_MATCH_OR_VALUE: + if (!strneq(line, "Match", 5)) + state = STATE_VALUE_OR_SECTION; + break; + case STATE_VALUE_OR_SECTION: + if (strneq(line, "Match", 5)) { + qlog_parser(ctx, "%s:%d: expected value or [Section], have %s\n", + path, lineno, line); + goto out; + } + break; + case STATE_ANY: + break; + } + + if (!parse_value_line(ctx, section, line)) { + qlog_parser(ctx, "%s:%d: failed to parse %s\n", + path, lineno, line); + goto out; + } + break; + default: + qlog_parser(ctx, "%s:%d: Unexpected line %s\n", + path, lineno, line); + goto out; + } + } + + if (!section) { + qlog_parser(ctx, "%s: is an empty file\n", path); + goto out; + } + + if ((!section->has_match || !section->has_property)) { + qlog_parser(ctx, "%s:%d: previous section %s was empty\n", + path, lineno, section->name); + goto out; /* Previous section was empty */ + } + + rc = true; +out: + if (fp) + fclose(fp); + + return rc; +} + +static int +is_data_file(const struct dirent *dir) { + const char *suffix = ".quirks"; + const int slen = strlen(suffix); + int offset; + + offset = strlen(dir->d_name) - slen; + if (offset <= 0) + return 0; + + return strneq(&dir->d_name[offset], suffix, slen); +} + +static inline bool +parse_files(struct quirks_context *ctx, const char *data_path) +{ + struct dirent **namelist; + int ndev = -1; + int idx = 0; + + ndev = scandir(data_path, &namelist, is_data_file, versionsort); + if (ndev <= 0) { + qlog_error(ctx, + "%s: failed to find data files\n", + data_path); + return false; + } + + for (idx = 0; idx < ndev; idx++) { + char path[PATH_MAX]; + + snprintf(path, + sizeof(path), + "%s/%s", + data_path, + namelist[idx]->d_name); + + if (!parse_file(ctx, path)) + break; + } + + for (int i = 0; i < ndev; i++) + free(namelist[i]); + free(namelist); + + return idx == ndev; +} + +struct quirks_context * +quirks_init_subsystem(const char *data_path, + const char *override_file, + libinput_log_handler log_handler, + struct libinput *libinput, + enum quirks_log_type log_type) +{ + struct quirks_context *ctx = zalloc(sizeof *ctx); + + ctx->refcount = 1; + ctx->log_handler = log_handler; + ctx->log_type = log_type; + ctx->libinput = libinput; + list_init(&ctx->quirks); + list_init(&ctx->sections); + + qlog_debug(ctx, "%s is data root\n", data_path); + + ctx->dmi = init_dmi(); + if (!ctx->dmi) + goto error; + + if (!parse_files(ctx, data_path)) + goto error; + + if (override_file && !parse_file(ctx, override_file)) + goto error; + + return ctx; + +error: + quirks_context_unref(ctx); + return NULL; +} + +struct quirks_context * +quirks_context_ref(struct quirks_context *ctx) +{ + assert(ctx->refcount > 0); + ctx->refcount++; + + return ctx; +} + +struct quirks_context * +quirks_context_unref(struct quirks_context *ctx) +{ + struct section *s, *tmp; + + if (!ctx) + return NULL; + + assert(ctx->refcount >= 1); + ctx->refcount--; + + if (ctx->refcount > 0) + return NULL; + + /* Caller needs to clean up before calling this */ + assert(list_empty(&ctx->quirks)); + + list_for_each_safe(s, tmp, &ctx->sections, link) { + section_destroy(s); + } + + free(ctx->dmi); + free(ctx); + + return NULL; +} + +static struct quirks * +quirks_new(void) +{ + struct quirks *q; + + q = zalloc(sizeof *q); + q->refcount = 1; + q->nproperties = 0; + list_init(&q->link); + + return q; +} + +struct quirks * +quirks_unref(struct quirks *q) +{ + struct property *p; + + if (!q) + return NULL; + + /* We don't really refcount, but might + * as well have the API in place */ + assert(q->refcount == 1); + + for (size_t i = 0; i < q->nproperties; i++) { + property_unref(q->properties[i]); + p++; + } + + list_remove(&q->link); + free(q->properties); + free(q); + + return NULL; +} + +/** + * Searches for the udev property on this device and its parent devices. + * + * @return the value of the property or NULL + */ +static const char * +udev_prop(struct udev_device *device, const char *prop) +{ + struct udev_device *d = device; + const char *value = NULL; + + do { + value = udev_device_get_property_value(d, prop); + d = udev_device_get_parent(d); + } while (value == NULL && d != NULL); + + return value; +} + +static inline void +match_fill_name(struct match *m, + struct udev_device *device) +{ + const char *str = udev_prop(device, "NAME"); + size_t slen; + + if (!str) + return; + + /* udev NAME is in quotes, strip them */ + if (str[0] == '"') + str++; + + m->name = safe_strdup(str); + slen = strlen(m->name); + if (slen > 1 && + m->name[slen - 1] == '"') + m->name[slen - 1] = '\0'; + + m->bits |= M_NAME; +} + +static inline void +match_fill_bus_vid_pid(struct match *m, + struct udev_device *device) +{ + const char *str; + unsigned int product, vendor, bus, version; + + str = udev_prop(device, "PRODUCT"); + if (!str) + return; + + /* ID_VENDOR_ID/ID_PRODUCT_ID/ID_BUS aren't filled in for virtual + * devices so we have to resort to PRODUCT */ + if (sscanf(str, "%x/%x/%x/%x", &bus, &vendor, &product, &version) != 4) + return; + + m->product = product; + m->vendor = vendor; + m->bits |= M_PID|M_VID; + switch (bus) { + case BUS_USB: + m->bus = BT_USB; + m->bits |= M_BUS; + break; + case BUS_BLUETOOTH: + m->bus = BT_BLUETOOTH; + m->bits |= M_BUS; + break; + case BUS_I8042: + m->bus = BT_PS2; + m->bits |= M_BUS; + break; + default: + break; + } +} + +static inline void +match_fill_udev_type(struct match *m, + struct udev_device *device) +{ + struct ut_map { + const char *prop; + enum udev_type flag; + } mappings[] = { + { "ID_INPUT_MOUSE", UDEV_MOUSE }, + { "ID_INPUT_POINTINGSTICK", UDEV_POINTINGSTICK }, + { "ID_INPUT_TOUCHPAD", UDEV_TOUCHPAD }, + { "ID_INPUT_TABLET", UDEV_TABLET }, + { "ID_INPUT_TABLET_PAD", UDEV_TABLET_PAD }, + { "ID_INPUT_JOYSTICK", UDEV_JOYSTICK }, + { "ID_INPUT_KEYBOARD", UDEV_KEYBOARD }, + }; + struct ut_map *map; + + ARRAY_FOR_EACH(mappings, map) { + if (udev_prop(device, map->prop)) + m->udev_type |= map->flag; + } + m->bits |= M_UDEV_TYPE; +} + +static inline void +match_fill_dmi(struct match *m, + char *dmi) +{ + m->dmi = dmi; + m->bits |= M_DMI; +} + +static struct match * +match_new(struct udev_device *device, + char *dmi) +{ + struct match *m = zalloc(sizeof *m); + + match_fill_name(m, device); + match_fill_bus_vid_pid(m, device); + match_fill_dmi(m, dmi); + match_fill_udev_type(m, device); + /* FIXME: dt */ + return m; +} + +static void +match_free(struct match *m) +{ + free(m->name); + free(m->dt); + free(m); +} + +static void +quirk_apply_section(struct quirks_context *ctx, + struct quirks *q, + const struct section *s) +{ + struct property *p; + size_t nprops = 0; + void *tmp; + + list_for_each(p, &s->properties, link) { + nprops++; + } + + nprops += q->nproperties; + tmp = reallocarray(q->properties, nprops, sizeof(p)); + if (!tmp) + return; + + q->properties = tmp; + list_for_each(p, &s->properties, link) { + qlog_debug(ctx, "property added: %s from %s\n", + quirk_get_name(p->id), s->name); + + q->properties[q->nproperties++] = property_ref(p); + } +} + +static bool +quirk_match_section(struct quirks_context *ctx, + struct quirks *q, + struct section *s, + struct match *m, + struct udev_device *device) +{ + uint32_t matched_flags = 0x0; + + for (uint32_t flag = 0x1; flag <= M_LAST; flag <<= 1) { + uint32_t prev_matched_flags = matched_flags; + /* section doesn't have this bit set, continue */ + if ((s->match.bits & flag) == 0) + continue; + + /* Couldn't fill in this bit for the match, so we + * do not match on it */ + if ((m->bits & flag) == 0) { + qlog_debug(ctx, + "%s wants %s but we don't have that\n", + s->name, matchflagname(flag)); + continue; + } + + /* now check the actual matching bit */ + switch (flag) { + case M_NAME: + if (fnmatch(s->match.name, m->name, 0) == 0) + matched_flags |= flag; + break; + case M_BUS: + if (m->bus == s->match.bus) + matched_flags |= flag; + break; + case M_VID: + if (m->vendor == s->match.vendor) + matched_flags |= flag; + break; + case M_PID: + if (m->product == s->match.product) + matched_flags |= flag; + break; + case M_DMI: + if (fnmatch(s->match.dmi, m->dmi, 0) == 0) + matched_flags |= flag; + break; + case M_UDEV_TYPE: + if (s->match.udev_type & m->udev_type) + matched_flags |= flag; + break; + case M_DT: + /* FIXME */ + break; + default: + abort(); + } + + if (prev_matched_flags != matched_flags) { + qlog_debug(ctx, + "%s matches for %s\n", + s->name, + matchflagname(flag)); + } + } + + if (s->match.bits == matched_flags) { + qlog_debug(ctx, "%s is full match\n", s->name); + quirk_apply_section(ctx, q, s); + } + + return true; +} + +struct quirks * +quirks_fetch_for_device(struct quirks_context *ctx, + struct udev_device *udev_device) +{ + struct quirks *q = NULL; + struct section *s; + struct match *m; + + if (!ctx) + return NULL; + + qlog_debug(ctx, "%s: fetching quirks\n", + udev_device_get_devnode(udev_device)); + + q = quirks_new(); + + m = match_new(udev_device, ctx->dmi); + + list_for_each(s, &ctx->sections, link) { + quirk_match_section(ctx, q, s, m, udev_device); + } + + match_free(m); + + if (q->nproperties == 0) { + quirks_unref(q); + return NULL; + } + + list_insert(&ctx->quirks, &q->link); + + return q; +} + + +static inline struct property * +quirk_find_prop(struct quirks *q, enum quirk which) +{ + /* Run backwards to only handle the last one assigned */ + for (ssize_t i = q->nproperties - 1; i >= 0; i--) { + struct property *p = q->properties[i]; + if (p->id == which) + return p; + } + + return NULL; +} + +bool +quirks_has_quirk(struct quirks *q, enum quirk which) +{ + return quirk_find_prop(q, which) != NULL; +} + +bool +quirks_get_int32(struct quirks *q, enum quirk which, int32_t *val) +{ + struct property *p; + + if (!q) + return false; + + p = quirk_find_prop(q, which); + if (!p) + return false; + + assert(p->type == PT_INT); + *val = p->value.i; + + return true; +} + +bool +quirks_get_uint32(struct quirks *q, enum quirk which, uint32_t *val) +{ + struct property *p; + + if (!q) + return false; + + p = quirk_find_prop(q, which); + if (!p) + return false; + + assert(p->type == PT_UINT); + *val = p->value.u; + + return true; +} + +bool +quirks_get_string(struct quirks *q, enum quirk which, char **val) +{ + struct property *p; + + if (!q) + return false; + + p = quirk_find_prop(q, which); + if (!p) + return false; + + assert(p->type == PT_STRING); + *val = p->value.s; + + return true; +} + +bool +quirks_get_bool(struct quirks *q, enum quirk which, bool *val) +{ + struct property *p; + + if (!q) + return false; + + p = quirk_find_prop(q, which); + if (!p) + return false; + + assert(p->type == PT_BOOL); + *val = p->value.b; + + return true; +} + +bool +quirks_get_dimensions(struct quirks *q, + enum quirk which, + struct quirk_dimensions *val) +{ + struct property *p; + + if (!q) + return false; + + p = quirk_find_prop(q, which); + if (!p) + return false; + + assert(p->type == PT_DIMENSION); + *val = p->value.dim; + + return true; +} + +bool +quirks_get_range(struct quirks *q, + enum quirk which, + struct quirk_range *val) +{ + struct property *p; + + if (!q) + return false; + + p = quirk_find_prop(q, which); + if (!p) + return false; + + assert(p->type == PT_RANGE); + *val = p->value.range; + + return true; +} diff --git a/src/quirks.h b/src/quirks.h new file mode 100644 index 00000000..c7c0a09c --- /dev/null +++ b/src/quirks.h @@ -0,0 +1,273 @@ +/* + * Copyright © 2018 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#pragma once + +#include "config.h" + +#include +#include + +#include + +#include "libinput.h" + +/** + * Handle to the quirks context. + */ +struct quirks_context; + +/** + * Contains all quirks set for a single device. + */ +struct quirks; + +struct quirk_dimensions { + size_t x, y; +}; + +struct quirk_range { + int lower, upper; +}; + +/** + * Quirks known to libinput + */ +enum quirk { + QUIRK_MODEL_ALPS_TOUCHPAD = 100, + QUIRK_MODEL_APPLE_TOUCHPAD, + QUIRK_MODEL_APPLE_MAGICMOUSE, + QUIRK_MODEL_TABLET_NO_TILT, + QUIRK_MODEL_APPLE_TOUCHPAD_ONEBUTTON, + QUIRK_MODEL_TOUCHPAD_VISIBLE_MARKER, + QUIRK_MODEL_CYBORG_RAT, + QUIRK_MODEL_CHROMEBOOK, + QUIRK_MODEL_HP6910_TOUCHPAD, + QUIRK_MODEL_HP8510_TOUCHPAD, + QUIRK_MODEL_HP_PAVILION_DM4_TOUCHPAD, + QUIRK_MODEL_HP_STREAM11_TOUCHPAD, + QUIRK_MODEL_HP_ZBOOK_STUDIO_G3, + QUIRK_MODEL_TABLET_NO_PROXIMITY_OUT, + QUIRK_MODEL_LENOVO_SCROLLPOINT, + QUIRK_MODEL_LENOVO_X230, + QUIRK_MODEL_LENOVO_T450_TOUCHPAD, + QUIRK_MODEL_TABLET_MODE_NO_SUSPEND, + QUIRK_MODEL_LENOVO_CARBON_X1_6TH, + QUIRK_MODEL_TRACKBALL, + QUIRK_MODEL_LOGITECH_MARBLE_MOUSE, + QUIRK_MODEL_BOUNCING_KEYS, + QUIRK_MODEL_SYNAPTICS_SERIAL_TOUCHPAD, + QUIRK_MODEL_SYSTEM76_BONOBO, + QUIRK_MODEL_CLEVO_W740SU, + QUIRK_MODEL_SYSTEM76_GALAGO, + QUIRK_MODEL_SYSTEM76_KUDU, + QUIRK_MODEL_WACOM_TOUCHPAD, + QUIRK_MODEL_JUMPING_SEMI_MT, + + + QUIRK_ATTR_SIZE_HINT = 300, + QUIRK_ATTR_TOUCH_SIZE_RANGE, + QUIRK_ATTR_PALM_SIZE_THRESHOLD, + QUIRK_ATTR_LID_SWITCH_RELIABILITY, + QUIRK_ATTR_KEYBOARD_INTEGRATION, + QUIRK_ATTR_TPKBCOMBO_LAYOUT, + QUIRK_ATTR_PRESSURE_RANGE, + QUIRK_ATTR_PALM_PRESSURE_THRESHOLD, + QUIRK_ATTR_RESOLUTION_HINT, + QUIRK_ATTR_TRACKPOINT_RANGE, + QUIRK_ATTR_THUMB_PRESSURE_THRESHOLD, +}; + +/** + * Returns a printable name for the quirk. This name is for developer + * tools, not user consumption. Do not display this in a GUI. + */ +const char* +quirk_get_name(enum quirk which); + +/** + * Log priorities used if custom logging is enabled. + */ +enum quirks_log_priorities { + QLOG_NOISE, + QLOG_DEBUG = LIBINPUT_LOG_PRIORITY_DEBUG, + QLOG_INFO = LIBINPUT_LOG_PRIORITY_INFO, + QLOG_ERROR = LIBINPUT_LOG_PRIORITY_ERROR, + QLOG_PARSER_ERROR, +}; + +/** + * Log type to be used for logging. Use the libinput logging to hook up a + * libinput log handler. This will cause the quirks to reduce the noise and + * only provide useful messages. + * + * QLOG_CUSTOM_LOG_PRIORITIES enables more fine-grained and verbose logging, + * allowing debugging tools to be more useful. + */ +enum quirks_log_type { + QLOG_LIBINPUT_LOGGING, + QLOG_CUSTOM_LOG_PRIORITIES, +}; + +/** + * Initialize the quirks subsystem. This function must be called + * before anything else. + * + * If log_type is QLOG_CUSTOM_LOG_PRIORITIES, the log handler is called with + * the custom QLOG_* log priorities. Otherwise, the log handler only uses + * the libinput log priorities. + * + * @param data_path The directory containing the various data files + * @param override_file A file path containing custom overrides + * @param log_handler The libinput log handler called for debugging output + * @param libinput The libinput struct passed to the log handler + * + * @return an opaque handle to the context + */ +struct quirks_context * +quirks_init_subsystem(const char *data_path, + const char *override_file, + libinput_log_handler log_handler, + struct libinput *libinput, + enum quirks_log_type log_type); + +/** + * Clean up after ourselves. This function must be called + * as the last call to the quirks subsystem. + * + * All quirks returned to the caller in quirks_fetch_for_device() must be + * unref'd before this call. + * + * @return Always NULL + */ +struct quirks_context * +quirks_context_unref(struct quirks_context *ctx); + +struct quirks_context * +quirks_context_ref(struct quirks_context *ctx); + +/** + * Fetch the quirks for a given device. If no quirks are defined, this + * function returns NULL. + * + * @return A new quirks struct, use quirks_unref() to release + */ +struct quirks * +quirks_fetch_for_device(struct quirks_context *ctx, + struct udev_device *device); + +/** + * Reduce the refcount by one. When the refcount reaches zero, the + * associated struct is released. + * + * @return Always NULL + */ +struct quirks * +quirks_unref(struct quirks *q); + +/** + * Returns true if the given quirk applies is in this quirk list. + */ +bool +quirks_has_quirk(struct quirks *q, enum quirk which); + +/** + * Get the value of the given quirk, as unsigned integer. + * This function will assert if the quirk type does not match the + * requested type. If the quirk is not set for this device, val is + * unchanged. + * + * @return true if the quirk value is valid, false otherwise. + */ +bool +quirks_get_uint32(struct quirks *q, + enum quirk which, + uint32_t *val); + +/** + * Get the value of the given quirk, as signed integer. + * This function will assert if the quirk type does not match the + * requested type. If the quirk is not set for this device, val is + * unchanged. + * + * @return true if the quirk value is valid, false otherwise. + */ +bool +quirks_get_int32(struct quirks *q, + enum quirk which, + int32_t *val); + +/** + * Get the value of the given quirk, as string. + * This function will assert if the quirk type does not match the + * requested type. If the quirk is not set for this device, val is + * unchanged. + * + * val is set to the string, do not modify or free it. The lifetime of the + * returned string is bound to the lifetime of the quirk. + * + * @return true if the quirk value is valid, false otherwise. + */ +bool +quirks_get_string(struct quirks *q, + enum quirk which, + char **val); + +/** + * Get the value of the given quirk, as bool. + * This function will assert if the quirk type does not match the + * requested type. If the quirk is not set for this device, val is + * unchanged. + * + * @return true if the quirk value is valid, false otherwise. + */ +bool +quirks_get_bool(struct quirks *q, + enum quirk which, + bool *val); + +/** + * Get the value of the given quirk, as dimension. + * This function will assert if the quirk type does not match the + * requested type. If the quirk is not set for this device, val is + * unchanged. + * + * @return true if the quirk value is valid, false otherwise. + */ +bool +quirks_get_dimensions(struct quirks *q, + enum quirk which, + struct quirk_dimensions *val); + +/** + * Get the value of the given quirk, as range. + * This function will assert if the quirk type does not match the + * requested type. If the quirk is not set for this device, val is + * unchanged. + * + * @return true if the quirk value is valid, false otherwise. + */ +bool +quirks_get_range(struct quirks *q, + enum quirk which, + struct quirk_range *val); diff --git a/test/test-quirks.c b/test/test-quirks.c new file mode 100644 index 00000000..646559ed --- /dev/null +++ b/test/test-quirks.c @@ -0,0 +1,809 @@ +/* + * Copyright © 2018 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include + +#include +#include + +#include "libinput-util.h" +#include "litest.h" +#include "quirks.h" + +static void +log_handler(struct libinput *this_is_null, + enum libinput_log_priority priority, + const char *format, + va_list args) +{ +#if 0 + vprintf(format, args); +#endif +} + +struct data_dir { + char *dirname; + char *filename; +}; + +static struct data_dir +make_data_dir(const char *file_content) +{ + struct data_dir dir = {0}; + char dirname[PATH_MAX] = "/run/litest-quirk-test-XXXXXX"; + char *filename; + FILE *fp; + int rc; + + litest_assert_notnull(mkdtemp(dirname)); + dir.dirname = safe_strdup(dirname); + + if (file_content) { + rc = xasprintf(&filename, "%s/testfile.quirks", dirname); + litest_assert_int_eq(rc, (int)(strlen(dirname) + 16)); + + fp = fopen(filename, "w+"); + rc = fputs(file_content, fp); + fclose(fp); + litest_assert_int_ge(rc, 0); + dir.filename = filename; + } + + return dir; +} + +static void +cleanup_data_dir(struct data_dir dd) +{ + if (dd.filename) { + unlink(dd.filename); + free(dd.filename); + } + if (dd.dirname) { + rmdir(dd.dirname); + free(dd.dirname); + } +} + +START_TEST(quirks_invalid_dir) +{ + struct quirks_context *ctx; + + ctx = quirks_init_subsystem("/does-not-exist", + NULL, + log_handler, + NULL, + QLOG_LIBINPUT_LOGGING); + ck_assert(ctx == NULL); +} +END_TEST + +START_TEST(quirks_empty_dir) +{ + struct quirks_context *ctx; + struct data_dir dd = make_data_dir(NULL); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_LIBINPUT_LOGGING); + ck_assert(ctx == NULL); + + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_section_empty) +{ + struct quirks_context *ctx; + const char quirks_file[] = "[Empty Section]"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_section_double) +{ + struct quirks_context *ctx; + const char quirks_file[] = "[Section name]"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_section_missing_match) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "AttrSizeHint=10x10\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_section_missing_attr) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchUdevType=mouse\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_section_match_after_attr) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchUdevType=mouse\n" + "AttrSizeHint=10x10\n" + "MatchName=mouse\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_section_duplicate_match) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchUdevType=mouse\n" + "MatchUdevType=mouse\n" + "AttrSizeHint=10x10\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_section_duplicate_attr) +{ + /* This shouldn't be allowed but the current parser + is happy with it */ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchUdevType=mouse\n" + "AttrSizeHint=10x10\n" + "AttrSizeHint=10x10\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert_notnull(ctx); + quirks_context_unref(ctx); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_parse_error_section) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section Missing Bracket\n" + "MatchUdevType=mouse\n" + "AttrSizeHint=10x10\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_parse_error_unknown_match) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "Matchblahblah=mouse\n" + "AttrSizeHint=10x10\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_parse_error_unknown_attr) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchUdevType=mouse\n" + "Attrblahblah=10x10\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_parse_error_unknown_model) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchUdevType=mouse\n" + "Modelblahblah=1\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_parse_error_model_not_one) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchUdevType=mouse\n" + "ModelAppleTouchpad=true\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_parse_bustype) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchBus=usb\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchBus=bluetooth\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchBus=i2c\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchBus=rmi\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchBus=ps2\n" + "ModelAppleTouchpad=1\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert_notnull(ctx); + quirks_context_unref(ctx); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_parse_bustype_invalid) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchBustype=venga\n" + "ModelAppleTouchpad=1\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_parse_vendor) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchVendor=0x0000\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchVendor=0x0001\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchVendor=0x2343\n" + "ModelAppleTouchpad=1\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert_notnull(ctx); + quirks_context_unref(ctx); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_parse_vendor_invalid) +{ + struct quirks_context *ctx; + const char *quirks_file[] = { + "[Section name]\n" + "MatchVendor=-1\n" + "ModelAppleTouchpad=1\n", + "[Section name]\n" + "MatchVendor=abc\n" + "ModelAppleTouchpad=1\n", + "[Section name]\n" + "MatchVendor=0xFFFFF\n" + "ModelAppleTouchpad=1\n", + "[Section name]\n" + "MatchVendor=123\n" + "ModelAppleTouchpad=1\n", + }; + const char **qf; + + ARRAY_FOR_EACH(quirks_file, qf) { + struct data_dir dd = make_data_dir(*qf); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); + } +} +END_TEST + +START_TEST(quirks_parse_product) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchProduct=0x0000\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchProduct=0x0001\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchProduct=0x2343\n" + "ModelAppleTouchpad=1\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert_notnull(ctx); + quirks_context_unref(ctx); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_parse_product_invalid) +{ + struct quirks_context *ctx; + const char *quirks_file[] = { + "[Section name]\n" + "MatchProduct=-1\n" + "ModelAppleTouchpad=1\n", + "[Section name]\n" + "MatchProduct=abc\n" + "ModelAppleTouchpad=1\n", + "[Section name]\n" + "MatchProduct=0xFFFFF\n" + "ModelAppleTouchpad=1\n", + "[Section name]\n" + "MatchProduct=123\n" + "ModelAppleTouchpad=1\n", + }; + const char **qf; + + ARRAY_FOR_EACH(quirks_file, qf) { + struct data_dir dd = make_data_dir(*qf); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); + } +} +END_TEST + +START_TEST(quirks_parse_name) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchName=1235\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchName=abc\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchName=*foo\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchName=foo*\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchName=foo[]\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchName=*foo*\n" + "ModelAppleTouchpad=1\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert_notnull(ctx); + quirks_context_unref(ctx); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_parse_name_invalid) +{ + struct quirks_context *ctx; + const char *quirks_file[] = { + "[Section name]\n" + "MatchName=\n" + "ModelAppleTouchpad=1\n", + }; + const char **qf; + + ARRAY_FOR_EACH(quirks_file, qf) { + struct data_dir dd = make_data_dir(*qf); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); + } +} +END_TEST + +START_TEST(quirks_parse_udev) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchUdevType=touchpad\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchUdevType=mouse\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchUdevType=pointingstick\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchUdevType=tablet\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchUdevType=tablet-pad\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchUdevType=keyboard\n" + "ModelAppleTouchpad=1\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert_notnull(ctx); + quirks_context_unref(ctx); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_parse_udev_invalid) +{ + struct quirks_context *ctx; + const char *quirks_file[] = { + "[Section name]\n" + "MatchUdevType=blah\n" + "ModelAppleTouchpad=1\n", + "[Section name]\n" + "MatchUdevType=\n" + "ModelAppleTouchpad=1\n", + "[Section name]\n" + "MatchUdevType=123\n" + "ModelAppleTouchpad=1\n", + }; + const char **qf; + + ARRAY_FOR_EACH(quirks_file, qf) { + struct data_dir dd = make_data_dir(*qf); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); + } +} +END_TEST + +START_TEST(quirks_parse_dmi) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchDMIModalias=dmi:*\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchDMIModalias=dmi:*svn*pn*:\n" + "ModelAppleTouchpad=1\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert_notnull(ctx); + quirks_context_unref(ctx); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_parse_dmi_invalid) +{ + struct quirks_context *ctx; + const char *quirks_file[] = { + "[Section name]\n" + "MatchDMIModalias=\n" + "ModelAppleTouchpad=1\n", + "[Section name]\n" + "MatchDMIModalias=*pn*\n" + "ModelAppleTouchpad=1\n", + "[Section name]\n" + "MatchDMIModalias=dmi*pn*\n" + "ModelAppleTouchpad=1\n", + "[Section name]\n" + "MatchDMIModalias=foo\n" + "ModelAppleTouchpad=1\n", + }; + const char **qf; + + ARRAY_FOR_EACH(quirks_file, qf) { + struct data_dir dd = make_data_dir(*qf); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); + } +} +END_TEST + +START_TEST(quirks_model_one) +{ + struct litest_device *dev = litest_current_device(); + struct udev_device *ud = libinput_device_get_udev_device(dev->libinput_device); + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchUdevType=mouse\n" + "ModelAppleTouchpad=1\n"; + struct data_dir dd = make_data_dir(quirks_file); + struct quirks *q; + bool isset; + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert_notnull(ctx); + + q = quirks_fetch_for_device(ctx, ud); + ck_assert_notnull(q); + + ck_assert(quirks_get_bool(q, QUIRK_MODEL_APPLE_TOUCHPAD, &isset)); + ck_assert(isset == true); + + quirks_unref(q); + quirks_context_unref(ctx); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_model_zero) +{ + struct litest_device *dev = litest_current_device(); + struct udev_device *ud = libinput_device_get_udev_device(dev->libinput_device); + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchUdevType=mouse\n" + "ModelAppleTouchpad=0\n"; + struct data_dir dd = make_data_dir(quirks_file); + struct quirks *q; + bool isset; + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert_notnull(ctx); + + q = quirks_fetch_for_device(ctx, ud); + ck_assert_notnull(q); + + ck_assert(quirks_get_bool(q, QUIRK_MODEL_APPLE_TOUCHPAD, &isset)); + ck_assert(isset == false); + + quirks_unref(q); + quirks_context_unref(ctx); + cleanup_data_dir(dd); +} +END_TEST + +TEST_COLLECTION(quirks) +{ + litest_add_for_device("quirks:datadir", quirks_invalid_dir, LITEST_MOUSE); + litest_add_for_device("quirks:datadir", quirks_empty_dir, LITEST_MOUSE); + + litest_add_for_device("quirks:structure", quirks_section_empty, LITEST_MOUSE); + litest_add_for_device("quirks:structure", quirks_section_double, LITEST_MOUSE); + litest_add_for_device("quirks:structure", quirks_section_missing_match, LITEST_MOUSE); + litest_add_for_device("quirks:structure", quirks_section_missing_attr, LITEST_MOUSE); + litest_add_for_device("quirks:structure", quirks_section_match_after_attr, LITEST_MOUSE); + litest_add_for_device("quirks:structure", quirks_section_duplicate_match, LITEST_MOUSE); + litest_add_for_device("quirks:structure", quirks_section_duplicate_attr, LITEST_MOUSE); + + litest_add_for_device("quirks:parsing", quirks_parse_error_section, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_error_unknown_match, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_error_unknown_attr, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_error_unknown_model, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_error_model_not_one, LITEST_MOUSE); + + litest_add_for_device("quirks:parsing", quirks_parse_bustype, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_bustype_invalid, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_vendor, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_vendor_invalid, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_product, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_product_invalid, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_name, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_name_invalid, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_udev, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_udev_invalid, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_dmi, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_dmi_invalid, LITEST_MOUSE); + + litest_add_for_device("quirks:model", quirks_model_one, LITEST_MOUSE); + litest_add_for_device("quirks:model", quirks_model_zero, LITEST_MOUSE); +} diff --git a/tools/libinput-list-quirks.c b/tools/libinput-list-quirks.c new file mode 100644 index 00000000..73e84895 --- /dev/null +++ b/tools/libinput-list-quirks.c @@ -0,0 +1,245 @@ +/* + * Copyright © 2018 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "libinput-util.h" +#include "quirks.h" + +static bool verbose = false; + +static void +log_handler(struct libinput *this_is_null, + enum libinput_log_priority priority, + const char *format, + va_list args) +{ + FILE *out = stdout; + enum quirks_log_priorities p = priority; + char buf[256] = {0}; + const char *prefix = ""; + + switch (p) { + case QLOG_NOISE: + case QLOG_DEBUG: + if (!verbose) + return; + prefix = "quirks debug"; + break; + case QLOG_INFO: + prefix = "quirks info"; + break; + case QLOG_ERROR: + out = stderr; + prefix = "quirks error"; + break; + case QLOG_PARSER_ERROR: + out = stderr; + prefix = "quirks parser error"; + break; + } + + snprintf(buf, sizeof(buf), "%s: %s", prefix, format); + vfprintf(out, buf, args); +} + +static void +list_device_quirks(struct quirks_context *ctx, struct udev_device *device) +{ + struct quirks *quirks; + + quirks = quirks_fetch_for_device(ctx, device); + if (!quirks) { + printf("Device has no quirks defined\n"); + } else { + enum quirk qlist[] = { + QUIRK_MODEL_ALPS_TOUCHPAD, + QUIRK_MODEL_APPLE_TOUCHPAD, + QUIRK_MODEL_APPLE_MAGICMOUSE, + QUIRK_MODEL_TABLET_NO_TILT, + QUIRK_MODEL_APPLE_TOUCHPAD_ONEBUTTON, + QUIRK_MODEL_TOUCHPAD_VISIBLE_MARKER, + QUIRK_MODEL_CYBORG_RAT, + QUIRK_MODEL_CHROMEBOOK, + QUIRK_MODEL_HP6910_TOUCHPAD, + QUIRK_MODEL_HP8510_TOUCHPAD, + QUIRK_MODEL_HP_PAVILION_DM4_TOUCHPAD, + QUIRK_MODEL_HP_STREAM11_TOUCHPAD, + QUIRK_MODEL_HP_ZBOOK_STUDIO_G3, + QUIRK_MODEL_TABLET_NO_PROXIMITY_OUT, + QUIRK_MODEL_LENOVO_SCROLLPOINT, + QUIRK_MODEL_LENOVO_X230, + QUIRK_MODEL_LENOVO_T450_TOUCHPAD, + QUIRK_MODEL_TABLET_MODE_NO_SUSPEND, + QUIRK_MODEL_LENOVO_CARBON_X1_6TH, + QUIRK_MODEL_TRACKBALL, + QUIRK_MODEL_LOGITECH_MARBLE_MOUSE, + QUIRK_MODEL_BOUNCING_KEYS, + QUIRK_MODEL_SYNAPTICS_SERIAL_TOUCHPAD, + QUIRK_MODEL_SYSTEM76_BONOBO, + QUIRK_MODEL_CLEVO_W740SU, + QUIRK_MODEL_SYSTEM76_GALAGO, + QUIRK_MODEL_SYSTEM76_KUDU, + QUIRK_MODEL_WACOM_TOUCHPAD, + + + QUIRK_ATTR_SIZE_HINT, + QUIRK_ATTR_TOUCH_SIZE_RANGE, + QUIRK_ATTR_PALM_SIZE_THRESHOLD, + QUIRK_ATTR_LID_SWITCH_RELIABILITY, + QUIRK_ATTR_KEYBOARD_INTEGRATION, + QUIRK_ATTR_TPKBCOMBO_LAYOUT, + QUIRK_ATTR_PRESSURE_RANGE, + QUIRK_ATTR_PALM_PRESSURE_THRESHOLD, + QUIRK_ATTR_RESOLUTION_HINT, + QUIRK_ATTR_TRACKPOINT_RANGE, + }; + enum quirk *q; + + ARRAY_FOR_EACH(qlist, q) { + if (!quirks_has_quirk(quirks, *q)) + continue; + + printf("%s\n", quirk_get_name(*q)); + } + } + + quirks_unref(quirks); +} + +static void +usage(void) +{ + printf("Usage: %s [--data-dir /path/to/data/dir] /dev/input/event0\n", + program_invocation_short_name); + printf("Note: this tool also takes a syspath\n"); +} + +int +main(int argc, char **argv) +{ + struct udev *udev; + struct udev_device *device = NULL; + const char *path; + const char *data_path = NULL, + *override_file = NULL; + int rc = 1; + struct quirks_context *quirks; + + while (1) { + int c; + int option_index = 0; + enum { + OPT_VERBOSE, + OPT_DATADIR, + }; + static struct option opts[] = { + { "help", no_argument, 0, 'h' }, + { "verbose", no_argument, 0, OPT_VERBOSE }, + { "data-dir", required_argument, 0, OPT_DATADIR }, + { 0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "h", opts, &option_index); + if (c == -1) + break; + + switch(c) { + case '?': + exit(1); + break; + case 'h': + usage(); + exit(0); + break; + case OPT_VERBOSE: + verbose = true; + break; + case OPT_DATADIR: + data_path = optarg; + break; + default: + usage(); + return 1; + } + } + + if (optind >= argc) { + usage(); + return 1; + } + + /* Overriding the data dir means no custom override file */ + if (!data_path) { + data_path = LIBINPUT_DATA_DIR; + override_file = LIBINPUT_DATA_OVERRIDE_FILE; + } + + quirks = quirks_init_subsystem(data_path, + override_file, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + if (!quirks) { + fprintf(stderr, + "Failed to initialize the device quirks. " + "Please see the above errors " + "and/or re-run with --verbose for more details\n"); + return 1; + } + + udev = udev_new(); + path = argv[optind]; + if (strneq(path, "/sys/", 5)) { + device = udev_device_new_from_syspath(udev, path); + } else { + struct stat st; + if (stat(path, &st) < 0) { + fprintf(stderr, "Error: %s: %m\n", path); + goto out; + } + + device = udev_device_new_from_devnum(udev, 'c', st.st_rdev); + } + if (device) { + list_device_quirks(quirks, device); + rc = 0; + } else { + usage(); + rc = 1; + } + + udev_device_unref(device); +out: + udev_unref(udev); + + quirks_context_unref(quirks); + + return rc; +} diff --git a/tools/libinput-list-quirks.man b/tools/libinput-list-quirks.man new file mode 100644 index 00000000..9d90847e --- /dev/null +++ b/tools/libinput-list-quirks.man @@ -0,0 +1,28 @@ +.TH libinput-list-quirks "1" "" "libinput @LIBINPUT_VERSION@" "libinput Manual" +.SH NAME +libinput\-list\-quirks \- quirk debug helper for libinput +.SH SYNOPSIS +.B libinput list\-quirks [\-\-help] [\-\-data\-dir /path/to/dir] [\-\-verbose\fB] \fI/dev/input/event0\fB +.SH DESCRIPTION +.PP +The +.B "libinput list\-quirks" +tool parses the quirks file in \fIdata\-dir\fR and prints all quirks applied +to the given device. +.PP +This is a debugging tool only, its output and behavior may change at any +time. Do not rely on the output. +.SH OPTIONS +.TP 8 +.B \-\-data\-dir \fI/path/to/dir\fR +Use the given directory as data directory for quirks files. +.TP 8 +.B \-\-help +Print help +.TP 8 +.B \-\-verbose +Use verbose output, useful for debugging. +.SH LIBINPUT +Part of the +.B libinput(1) +suite From fc6e6aad363a612110e43dcc5f471c69f8560064 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 25 May 2018 09:23:50 +1000 Subject: [PATCH 06/14] quirks: use an empty dmi modalias string for the test suite We don't want any of the test devices to match the local machine's DMI modalias. This was a major drawback in the previous test suite, hacking the dmi modalias string was nontrivial but a wrong string could cause false positives or negatives. The quirks system is internal, so rather than having some fancy API we just hook it off the environment variable that the test suite always sets. Hacky, but a lot easier than the other options. Signed-off-by: Peter Hutterer --- src/quirks.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/quirks.c b/src/quirks.c index 7cef8a79..b88d8bc9 100644 --- a/src/quirks.c +++ b/src/quirks.c @@ -122,7 +122,7 @@ struct match { uint32_t vendor; uint32_t product; - char *dmi; + char *dmi; /* dmi modalias with preceding "dmi:" */ /* We can have more than one type set, so this is a bitfield */ uint32_t udev_type; @@ -337,10 +337,13 @@ init_dmi(void) { struct udev *udev; struct udev_device *udev_device; - const char *modalias; + const char *modalias = NULL; char *copy = NULL; const char *syspath = "/sys/devices/virtual/dmi/id"; + if (getenv("LIBINPUT_RUNNING_TEST_SUITE")) + return safe_strdup("dmi:"); + udev = udev_new(); if (!udev) return NULL; From 3ce70cfa91e823afd50c1f6a70b178a75a9015a9 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Thu, 31 May 2018 09:42:15 +1000 Subject: [PATCH 07/14] quirks: allow for in-line comments Signed-off-by: Peter Hutterer --- data/README.md | 1 - src/quirks.c | 22 +++++++++++++--- test/test-quirks.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 4 deletions(-) diff --git a/data/README.md b/data/README.md index 66897938..55b9056f 100644 --- a/data/README.md +++ b/data/README.md @@ -60,7 +60,6 @@ The following will cause parser errors and are considered invalid data files: * Whitespace at the beginning of the line -* Inline comments, e.g. `MatchBus=usb # oops, fail` * Sections without at least one `Match*` entry * Sections with the same `Match*` entry repeated * Sections without at least one of `Model*` or `Attr` entries diff --git a/src/quirks.c b/src/quirks.c index b88d8bc9..1a44574a 100644 --- a/src/quirks.c +++ b/src/quirks.c @@ -767,10 +767,26 @@ parse_file(struct quirks_context *ctx, const char *path) } while (fgets(line, sizeof(line), fp)) { - lineno++; - if (strlen(line) >= 1 && line[strlen(line) - 1] == '\n') - line[strlen(line) - 1] = '\0'; /* drop trailing \n */ + char *comment; + lineno++; + + comment = strstr(line, "#"); + if (comment) { + /* comment points to # but we need to remove the + * preceding whitespaces too */ + comment--; + while (comment >= line) { + if (*comment != ' ' && *comment != '\t') + break; + comment--; + } + *(comment + 1) = '\0'; + } else { /* strip the trailing newline */ + comment = strstr(line, "\n"); + if (comment) + *comment = '\0'; + } if (strlen(line) == 0) continue; diff --git a/test/test-quirks.c b/test/test-quirks.c index 646559ed..6c427dad 100644 --- a/test/test-quirks.c +++ b/test/test-quirks.c @@ -264,6 +264,25 @@ START_TEST(quirks_parse_error_section) } END_TEST +START_TEST(quirks_parse_error_trailing_whitespace) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchUdevType=mouse \n" + "AttrSizeHint=10x10\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); +} +END_TEST + START_TEST(quirks_parse_error_unknown_match) { struct quirks_context *ctx; @@ -340,6 +359,48 @@ START_TEST(quirks_parse_error_model_not_one) } END_TEST +START_TEST(quirks_parse_comment_inline) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name] # some inline comment\n" + "MatchUdevType=mouse\t # another inline comment\n" + "ModelAppleTouchpad=1#\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert_notnull(ctx); + quirks_context_unref(ctx); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_parse_comment_empty) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "#\n" + " #\n" + "MatchUdevType=mouse\n" + "ModelAppleTouchpad=1\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert_notnull(ctx); + quirks_context_unref(ctx); + cleanup_data_dir(dd); +} +END_TEST + START_TEST(quirks_parse_bustype) { struct quirks_context *ctx; @@ -786,10 +847,13 @@ TEST_COLLECTION(quirks) litest_add_for_device("quirks:structure", quirks_section_duplicate_attr, LITEST_MOUSE); litest_add_for_device("quirks:parsing", quirks_parse_error_section, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_error_trailing_whitespace, LITEST_MOUSE); litest_add_for_device("quirks:parsing", quirks_parse_error_unknown_match, LITEST_MOUSE); litest_add_for_device("quirks:parsing", quirks_parse_error_unknown_attr, LITEST_MOUSE); litest_add_for_device("quirks:parsing", quirks_parse_error_unknown_model, LITEST_MOUSE); litest_add_for_device("quirks:parsing", quirks_parse_error_model_not_one, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_comment_inline, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_comment_empty, LITEST_MOUSE); litest_add_for_device("quirks:parsing", quirks_parse_bustype, LITEST_MOUSE); litest_add_for_device("quirks:parsing", quirks_parse_bustype_invalid, LITEST_MOUSE); From 5e4dee22fd56a1f5fbf0b0d6a31be628e9369428 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Thu, 24 May 2018 15:36:22 +1000 Subject: [PATCH 08/14] test: install per-test device quirks files These will replace the custom udev rules we currently have in place. Signed-off-by: Peter Hutterer --- meson.build | 3 ++ test/litest-int.h | 1 + test/litest.c | 86 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+) diff --git a/meson.build b/meson.build index b8735ced..5e4db287 100644 --- a/meson.build +++ b/meson.build @@ -216,6 +216,9 @@ quirks_data = [ 'data/50-system-system76.quirks', ] +config_h.set_quoted('LIBINPUT_DATA_FILES', ':'.join(quirks_data)) +config_h.set_quoted('LIBINPUT_DATA_SRCDIR', join_paths(meson.source_root(), 'data')) + install_data(quirks_data, install_dir : libinput_data_path) src_libquirks = [ diff --git a/test/litest-int.h b/test/litest-int.h index 6384e4da..1cdae730 100644 --- a/test/litest-int.h +++ b/test/litest-int.h @@ -70,6 +70,7 @@ struct litest_test_device { struct litest_device_interface *interface; const char *udev_rule; + const char *quirk_file; }; struct litest_device_interface { diff --git a/test/litest.c b/test/litest.c index 9d711098..13f0a4fa 100644 --- a/test/litest.c +++ b/test/litest.c @@ -87,6 +87,7 @@ struct list created_files_list; /* list of all files to remove at the end of static void litest_init_udev_rules(struct list *created_files_list); static void litest_remove_udev_rules(struct list *created_files_list); +static char *litest_install_quirks(struct list *created_files_list); /* defined for the litest selftest */ #ifndef LITEST_DISABLE_BACKTRACE_LOGGING @@ -999,6 +1000,7 @@ litest_run(int argc, char **argv) { int failed = 0; int inhibit_lock_fd; + char *quirks_dir; list_init(&created_files_list); @@ -1012,6 +1014,10 @@ litest_run(int argc, char **argv) verbose = 1; litest_init_udev_rules(&created_files_list); + quirks_dir = litest_install_quirks(&created_files_list); + + setenv("LIBINPUT_DATA_DIR", quirks_dir, 1); + free(quirks_dir); litest_setup_sighandler(SIGINT); @@ -1177,6 +1183,86 @@ litest_install_model_quirks(struct list *created_files_list) list_insert(created_files_list, &file->link); } +static char * +litest_init_device_quirk_file(const char *data_dir, + struct litest_test_device *dev) +{ + int fd; + FILE *f; + char path[PATH_MAX]; + static int count; + + if (!dev->quirk_file) + return NULL; + + snprintf(path, sizeof(path), + "%s/99-%03d-%s.quirks", + data_dir, + ++count, + dev->shortname); + fd = open(path, O_CREAT|O_WRONLY, 0644); + litest_assert_int_ne(fd, -1); + f = fdopen(fd, "w"); + litest_assert_notnull(f); + litest_assert_int_ge(fputs(dev->quirk_file, f), 0); + fclose(f); + + return safe_strdup(path); +} + + +static char * +litest_install_quirks(struct list *created_files_list) +{ + struct litest_test_device **dev = devices; + struct created_file *file; + char dirname[PATH_MAX] = "/run/litest-XXXXXX"; + char **quirks, **q; + + litest_assert_notnull(mkdtemp(dirname)); + litest_assert_int_ne(chmod(dirname, 0755), -1); + + quirks = strv_from_string(LIBINPUT_DATA_FILES, ":"); + litest_assert(quirks); + + q = quirks; + while (*q) { + char *filename; + char dest[PATH_MAX]; + char src[PATH_MAX]; + + litest_assert(strneq(*q, "data/", 5)); + filename = &(*q)[5]; + + snprintf(src, sizeof(src), "%s/%s", LIBINPUT_DATA_SRCDIR, filename); + snprintf(dest, sizeof(dest), "%s/%s", dirname, filename); + file = litest_copy_file(dest, src, NULL); + list_append(created_files_list, &file->link); + q++; + } + strv_free(quirks); + + /* Now add the per-device special config files */ + + while (*dev) { + char *path; + + path = litest_init_device_quirk_file(dirname, *dev); + if (path) { + struct created_file *file = zalloc(sizeof(*file)); + file->path = path; + list_insert(created_files_list, &file->link); + } + dev++; + } + + file = zalloc(sizeof *file); + file->path = safe_strdup(dirname); + list_append(created_files_list, &file->link); + + return safe_strdup(dirname); +} + static inline void mkdir_p(const char *dir) { From a50e13d50f3f804ef68fbf0753d1c03afabe853b Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Thu, 24 May 2018 16:52:42 +1000 Subject: [PATCH 09/14] test: init the quirks once per test suite run So we have them available per litest device and can check in tests for certain quirks to be present. Signed-off-by: Peter Hutterer --- test/litest.c | 28 ++++++++++++++++++++++++++++ test/litest.h | 2 ++ 2 files changed, 30 insertions(+) diff --git a/test/litest.c b/test/litest.c index 13f0a4fa..91df2b1a 100644 --- a/test/litest.c +++ b/test/litest.c @@ -55,6 +55,7 @@ #include "litest.h" #include "litest-int.h" #include "libinput-util.h" +#include "quirks.h" #include @@ -76,6 +77,7 @@ static int verbose = 0; const char *filter_test = NULL; const char *filter_device = NULL; const char *filter_group = NULL; +static struct quirks_context *quirks_context; struct created_file { struct list link; @@ -790,6 +792,19 @@ litest_free_test_list(struct list *tests) } } +LIBINPUT_ATTRIBUTE_PRINTF(3, 0) +static inline void +quirk_log_handler(struct libinput *unused, + enum libinput_log_priority priority, + const char *format, + va_list args) +{ + if (priority < LIBINPUT_LOG_PRIORITY_ERROR) + return; + + vfprintf(stderr, format, args); +} + static int litest_run_suite(struct list *tests, int which, int max, int error_fd) { @@ -805,6 +820,12 @@ litest_run_suite(struct list *tests, int which, int max, int error_fd) struct name *n, *tmp; struct list testnames; + quirks_context = quirks_init_subsystem(getenv("LIBINPUT_DATA_DIR"), + NULL, + quirk_log_handler, + NULL, + QLOG_LIBINPUT_LOGGING); + /* Check just takes the suite/test name pointers but doesn't strdup * them - we have to keep them around */ list_init(&testnames); @@ -889,6 +910,8 @@ out: free(n); } + quirks_context_unref(quirks_context); + return failed; } @@ -1494,6 +1517,9 @@ litest_add_device_with_overrides(struct libinput *libinput, d->libinput = libinput; d->libinput_device = libinput_path_add_device(d->libinput, path); + d->quirks = quirks_fetch_for_device(quirks_context, + libinput_device_get_udev_device(d->libinput_device)); + litest_assert(d->libinput_device != NULL); libinput_device_ref(d->libinput_device); @@ -1617,6 +1643,8 @@ litest_delete_device(struct litest_device *d) litest_assert_int_eq(d->skip_ev_syn, 0); + quirks_unref(d->quirks); + if (d->libinput_device) { libinput_path_remove_device(d->libinput_device); libinput_device_unref(d->libinput_device); diff --git a/test/litest.h b/test/litest.h index 2315764b..def06f0b 100644 --- a/test/litest.h +++ b/test/litest.h @@ -35,6 +35,7 @@ #include #include "libinput-util.h" +#include "quirks.h" struct test_device { const char *name; @@ -345,6 +346,7 @@ struct litest_device { struct libevdev *evdev; struct libevdev_uinput *uinput; struct libinput *libinput; + struct quirks *quirks; bool owns_context; struct libinput_device *libinput_device; struct litest_device_interface *interface; From 12b07229d888d44630ba459b91c68c68c3fbfe07 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 25 May 2018 10:01:47 +1000 Subject: [PATCH 10/14] test: switch the udev tag tests to be quirk tests Signed-off-by: Peter Hutterer --- test/test-device.c | 85 ---------------------------------------------- test/test-quirks.c | 83 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 85 deletions(-) diff --git a/test/test-device.c b/test/test-device.c index 34aa9447..c072e83e 100644 --- a/test/test-device.c +++ b/test/test-device.c @@ -1141,87 +1141,6 @@ START_TEST(device_accelerometer) } END_TEST -START_TEST(device_udev_tag_alps) -{ - struct litest_device *dev = litest_current_device(); - struct libinput_device *device = dev->libinput_device; - struct udev_device *d; - const char *prop; - - d = libinput_device_get_udev_device(device); - prop = udev_device_get_property_value(d, - "LIBINPUT_MODEL_ALPS_TOUCHPAD"); - - if (strstr(libinput_device_get_name(device), "ALPS")) - ck_assert_notnull(prop); - else - ck_assert(prop == NULL); - - udev_device_unref(d); -} -END_TEST - -START_TEST(device_udev_tag_wacom) -{ - struct litest_device *dev = litest_current_device(); - struct libinput_device *device = dev->libinput_device; - struct udev_device *d; - const char *prop; - - d = libinput_device_get_udev_device(device); - prop = udev_device_get_property_value(d, - "LIBINPUT_MODEL_WACOM_TOUCHPAD"); - - if (libevdev_get_id_vendor(dev->evdev) == VENDOR_ID_WACOM) - ck_assert_notnull(prop); - else - ck_assert(prop == NULL); - - udev_device_unref(d); -} -END_TEST - -START_TEST(device_udev_tag_apple) -{ - struct litest_device *dev = litest_current_device(); - struct libinput_device *device = dev->libinput_device; - struct udev_device *d; - const char *prop; - - d = libinput_device_get_udev_device(device); - prop = udev_device_get_property_value(d, - "LIBINPUT_MODEL_APPLE_TOUCHPAD"); - - if (libevdev_get_id_vendor(dev->evdev) == VENDOR_ID_APPLE) - ck_assert_notnull(prop); - else - ck_assert(prop == NULL); - - udev_device_unref(d); -} -END_TEST - -START_TEST(device_udev_tag_synaptics_serial) -{ - struct litest_device *dev = litest_current_device(); - struct libinput_device *device = dev->libinput_device; - struct udev_device *d; - const char *prop; - - d = libinput_device_get_udev_device(device); - prop = udev_device_get_property_value(d, - "LIBINPUT_MODEL_SYNAPTICS_SERIAL_TOUCHPAD"); - - if (libevdev_get_id_vendor(dev->evdev) == VENDOR_ID_SYNAPTICS_SERIAL && - libevdev_get_id_product(dev->evdev) == PRODUCT_ID_SYNAPTICS_SERIAL) - ck_assert_notnull(prop); - else - ck_assert(prop == NULL); - - udev_device_unref(d); -} -END_TEST - START_TEST(device_udev_tag_wacom_tablet) { struct litest_device *dev = litest_current_device(); @@ -1627,10 +1546,6 @@ TEST_COLLECTION(device) litest_add("device:wheel", device_wheel_only, LITEST_WHEEL, LITEST_RELATIVE|LITEST_ABSOLUTE|LITEST_TABLET); litest_add_no_device("device:accelerometer", device_accelerometer); - litest_add("device:udev tags", device_udev_tag_alps, LITEST_TOUCHPAD, LITEST_ANY); - litest_add("device:udev tags", device_udev_tag_wacom, LITEST_TOUCHPAD, LITEST_ANY); - litest_add("device:udev tags", device_udev_tag_apple, LITEST_TOUCHPAD, LITEST_ANY); - litest_add("device:udev tags", device_udev_tag_synaptics_serial, LITEST_TOUCHPAD, LITEST_ANY); litest_add("device:udev tags", device_udev_tag_wacom_tablet, LITEST_TABLET, LITEST_ANY); litest_add_no_device("device:invalid rel events", device_nonpointer_rel); diff --git a/test/test-quirks.c b/test/test-quirks.c index 6c427dad..16bbacf0 100644 --- a/test/test-quirks.c +++ b/test/test-quirks.c @@ -833,6 +833,84 @@ START_TEST(quirks_model_zero) } END_TEST +START_TEST(quirks_model_alps) +{ + struct litest_device *dev = litest_current_device(); + struct libinput_device *device = dev->libinput_device; + struct quirks *q; + bool exists, value; + + q = dev->quirks; + exists = quirks_get_bool(q, QUIRK_MODEL_ALPS_TOUCHPAD, &value); + + if (strstr(libinput_device_get_name(device), "ALPS")) { + ck_assert(exists); + ck_assert(value); + } else { + ck_assert(!exists); + ck_assert(!value); + } +} +END_TEST + +START_TEST(quirks_model_wacom) +{ + struct litest_device *dev = litest_current_device(); + struct quirks *q; + bool exists, value; + + q = dev->quirks; + exists = quirks_get_bool(q, QUIRK_MODEL_WACOM_TOUCHPAD, &value); + + if (libevdev_get_id_vendor(dev->evdev) == VENDOR_ID_WACOM) { + ck_assert(exists); + ck_assert(value); + } else { + ck_assert(!exists); + ck_assert(!value); + } +} +END_TEST + +START_TEST(quirks_model_apple) +{ + struct litest_device *dev = litest_current_device(); + struct quirks *q; + bool exists, value; + + q = dev->quirks; + exists = quirks_get_bool(q, QUIRK_MODEL_APPLE_TOUCHPAD, &value); + + if (libevdev_get_id_vendor(dev->evdev) == VENDOR_ID_APPLE) { + ck_assert(exists); + ck_assert(value); + } else { + ck_assert(!exists); + ck_assert(!value); + } +} +END_TEST + +START_TEST(quirks_model_synaptics_serial) +{ + struct litest_device *dev = litest_current_device(); + struct quirks *q; + bool exists, value; + + q = dev->quirks; + exists = quirks_get_bool(q, QUIRK_MODEL_SYNAPTICS_SERIAL_TOUCHPAD, &value); + + if (libevdev_get_id_vendor(dev->evdev) == VENDOR_ID_SYNAPTICS_SERIAL && + libevdev_get_id_product(dev->evdev) == PRODUCT_ID_SYNAPTICS_SERIAL) { + ck_assert(exists); + ck_assert(value); + } else { + ck_assert(!exists); + ck_assert(!value); + } +} +END_TEST + TEST_COLLECTION(quirks) { litest_add_for_device("quirks:datadir", quirks_invalid_dir, LITEST_MOUSE); @@ -870,4 +948,9 @@ TEST_COLLECTION(quirks) litest_add_for_device("quirks:model", quirks_model_one, LITEST_MOUSE); litest_add_for_device("quirks:model", quirks_model_zero, LITEST_MOUSE); + + litest_add("quirks:devices", quirks_model_alps, LITEST_TOUCHPAD, LITEST_ANY); + litest_add("quirks:devices", quirks_model_wacom, LITEST_TOUCHPAD, LITEST_ANY); + litest_add("quirks:devices", quirks_model_apple, LITEST_TOUCHPAD, LITEST_ANY); + litest_add("quirks:devices", quirks_model_synaptics_serial, LITEST_TOUCHPAD, LITEST_ANY); } From 33341ddd20103f0ac447ec4beabe17463af89ca9 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Thu, 24 May 2018 11:46:31 +1000 Subject: [PATCH 11/14] libinput: initialize the quirks subsystem A bit quirky (haha), because we cannot do this during context creation - we really want any parsing error messages to show up in the right log file and the log handler isn't set up during context creation. So we do it on the first real call to the backend - path_add_device or udev_assign_seat. Also, failure to initialize the quirks subsystem just means we continue as normal. This shouldn't be a hard failure, it just means a lot of devices won't work properly. If the LIBINPUT_DATA_DIR environment variable is set, that directory is used for the data file. Only that directory, no custom override file in that case. Signed-off-by: Peter Hutterer --- src/libinput-private.h | 6 ++++++ src/libinput.c | 42 ++++++++++++++++++++++++++++++++++++++++++ src/path-seat.c | 7 +++++++ src/udev-seat.c | 7 +++++++ 4 files changed, 62 insertions(+) diff --git a/src/libinput-private.h b/src/libinput-private.h index d50154ef..6faf9e56 100644 --- a/src/libinput-private.h +++ b/src/libinput-private.h @@ -140,6 +140,9 @@ struct libinput { struct list device_group_list; uint64_t last_event_time; + + bool quirks_initialized; + struct quirks_context *quirks; }; typedef void (*libinput_seat_destroy_func) (struct libinput_seat *seat); @@ -427,6 +430,9 @@ libinput_init(struct libinput *libinput, const struct libinput_interface_backend *interface_backend, void *user_data); +void +libinput_init_quirks(struct libinput *libinput); + struct libinput_source * libinput_add_fd(struct libinput *libinput, int fd, diff --git a/src/libinput.c b/src/libinput.c index 78b54758..846c5bf7 100644 --- a/src/libinput.c +++ b/src/libinput.c @@ -38,6 +38,7 @@ #include "libinput-private.h" #include "evdev.h" #include "timer.h" +#include "quirks.h" #define require_event_type(li_, type_, retval_, ...) \ if (type_ == LIBINPUT_EVENT_NONE) abort(); \ @@ -1720,6 +1721,46 @@ libinput_init(struct libinput *libinput, return 0; } +void +libinput_init_quirks(struct libinput *libinput) +{ + const char *data_path, + *override_file = NULL; + struct quirks_context *quirks; + + if (libinput->quirks_initialized) + return; + + /* If we fail, we'll fail next time too */ + libinput->quirks_initialized = true; + + data_path = getenv("LIBINPUT_DATA_DIR"); + if (!data_path) { + data_path = LIBINPUT_DATA_DIR; + override_file = LIBINPUT_DATA_OVERRIDE_FILE; + } + + quirks = quirks_init_subsystem(data_path, + override_file, + log_msg_va, + libinput, + QLOG_LIBINPUT_LOGGING); + if (!quirks) { + log_error(libinput, + "Failed to load the device quirks from %s%s%s. " + "This will negatively affect device behavior. " + "See %sdevice-quirks.html for details.\n", + data_path, + override_file ? " and " : "", + override_file ? override_file : "", + HTTP_DOC_LINK + ); + return; + } + + libinput->quirks = quirks; +} + static void libinput_device_destroy(struct libinput_device *device); @@ -1791,6 +1832,7 @@ libinput_unref(struct libinput *libinput) libinput_timer_subsys_destroy(libinput); libinput_drop_destroyed_sources(libinput); + quirks_context_unref(libinput->quirks); close(libinput->epoll_fd); free(libinput); diff --git a/src/path-seat.c b/src/path-seat.c index 4322b920..6cc7b3e4 100644 --- a/src/path-seat.c +++ b/src/path-seat.c @@ -337,6 +337,13 @@ libinput_path_add_device(struct libinput *libinput, return NULL; } + /* We cannot do this during path_create_context because the log + * handler isn't set up there but we really want to log to the right + * place if the quirks run into parser errors. So we have to do it + * on the first call to add_device. + */ + libinput_init_quirks(libinput); + udev_device = udev_device_from_devnode(libinput, udev, path); if (!udev_device) { log_bug_client(libinput, "Invalid path %s\n", path); diff --git a/src/udev-seat.c b/src/udev-seat.c index 37d3dcce..aeae198c 100644 --- a/src/udev-seat.c +++ b/src/udev-seat.c @@ -382,6 +382,13 @@ libinput_udev_assign_seat(struct libinput *libinput, { struct udev_input *input = (struct udev_input*)libinput; + /* We cannot do this during udev_create_context because the log + * handler isn't set up there but we really want to log to the right + * place if the quirks run into parser errors. So we have to do it + * here since we can expect the log handler to be set up by now. + */ + libinput_init_quirks(libinput); + if (!seat_id) return -1; From 639ed0b641749dc836d5808e4f189b032701b904 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Thu, 24 May 2018 11:56:24 +1000 Subject: [PATCH 12/14] Switch from udev property parsing to the quirks system Signed-off-by: Peter Hutterer --- src/evdev-mt-touchpad.c | 164 ++++++++++--------- src/evdev.c | 155 ++++++++++++------ test/litest-device-alps-dualpoint.c | 15 +- test/litest-device-apple-appletouch.c | 15 +- test/litest-device-gpio-keys.c | 9 +- test/litest-device-huion-pentablet.c | 15 +- test/litest-device-lid-switch-surface3.c | 9 +- test/litest-device-lid-switch.c | 9 +- test/litest-device-synaptics-i2c.c | 15 +- test/litest-device-synaptics-x1-carbon-3rd.c | 15 +- test/litest-device-waltop-tablet.c | 15 +- test/test-switch.c | 13 +- test/test-tablet.c | 18 +- 13 files changed, 252 insertions(+), 215 deletions(-) diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index dd575510..85095b08 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -28,6 +28,7 @@ #include #include +#include "quirks.h" #include "evdev-mt-touchpad.h" #define DEFAULT_TRACKPOINT_ACTIVITY_TIMEOUT ms2us(300) @@ -2835,16 +2836,25 @@ tp_dwt_config_get_default(struct libinput_device *device) static inline bool tp_is_tpkb_combo_below(struct evdev_device *device) { - const char *prop; + struct quirks_context *quirks; + struct quirks *q; + char *prop; enum tpkbcombo_layout layout = TPKBCOMBO_LAYOUT_UNKNOWN; + int rc = false; - prop = udev_device_get_property_value(device->udev_device, - "LIBINPUT_ATTR_TPKBCOMBO_LAYOUT"); - if (!prop) + quirks = evdev_libinput_context(device)->quirks; + q = quirks_fetch_for_device(quirks, device->udev_device); + if (!q) return false; - return parse_tpkbcombo_layout_poperty(prop, &layout) && - layout == TPKBCOMBO_LAYOUT_BELOW; + if (quirks_get_string(q, QUIRK_ATTR_TPKBCOMBO_LAYOUT, &prop)) { + rc = parse_tpkbcombo_layout_poperty(prop, &layout) && + layout == TPKBCOMBO_LAYOUT_BELOW; + } + + quirks_unref(q); + + return rc; } static inline bool @@ -2911,19 +2921,20 @@ static int tp_read_palm_pressure_prop(struct tp_dispatch *tp, const struct evdev_device *device) { - struct udev_device *udev_device = device->udev_device; - const char *prop; - int threshold; const int default_palm_threshold = 130; + uint32_t threshold = default_palm_threshold; + struct quirks_context *quirks; + struct quirks *q; - prop = udev_device_get_property_value(udev_device, - "LIBINPUT_ATTR_PALM_PRESSURE_THRESHOLD"); - if (!prop) - return default_palm_threshold; + quirks = evdev_libinput_context(device)->quirks; + q = quirks_fetch_for_device(quirks, device->udev_device); + if (!q) + return threshold; - threshold = parse_palm_pressure_property(prop); + quirks_get_uint32(q, QUIRK_ATTR_PALM_PRESSURE_THRESHOLD, &threshold); + quirks_unref(q); - return threshold > 0 ? threshold : default_palm_threshold; + return threshold; } static inline void @@ -2947,24 +2958,26 @@ static inline void tp_init_palmdetect_size(struct tp_dispatch *tp, struct evdev_device *device) { - const char *prop; - int threshold; + struct quirks_context *quirks; + struct quirks *q; + uint32_t threshold; - prop = udev_device_get_property_value(device->udev_device, - "LIBINPUT_ATTR_PALM_SIZE_THRESHOLD"); - if (!prop) + quirks = evdev_libinput_context(device)->quirks; + q = quirks_fetch_for_device(quirks, device->udev_device); + if (!q) return; - threshold = parse_palm_size_property(prop); - if (threshold == 0) { - evdev_log_bug_client(device, - "palm: ignoring invalid threshold %s\n", - prop); - return; + if (quirks_get_uint32(q, QUIRK_ATTR_PALM_SIZE_THRESHOLD, &threshold)) { + if (threshold == 0) { + evdev_log_bug_client(device, + "palm: ignoring invalid threshold %d\n", + threshold); + } else { + tp->palm.use_size = true; + tp->palm.size_threshold = threshold; + } } - - tp->palm.use_size = true; - tp->palm.size_threshold = threshold; + quirks_unref(q); } static inline void @@ -3039,25 +3052,6 @@ tp_init_sendevents(struct tp_dispatch *tp, tp_keyboard_timeout, tp); } -static int -tp_read_thumb_pressure_prop(struct tp_dispatch *tp, - const struct evdev_device *device) -{ - struct udev_device *udev_device = device->udev_device; - const char *prop; - int threshold; - const int default_thumb_threshold = 0; - - prop = udev_device_get_property_value(udev_device, - "LIBINPUT_ATTR_THUMB_PRESSURE_THRESHOLD"); - if (!prop) - return default_thumb_threshold; - - threshold = parse_thumb_pressure_property(prop); - - return threshold > 0 ? threshold : default_thumb_threshold; -} - static void tp_init_thumb(struct tp_dispatch *tp) { @@ -3066,7 +3060,9 @@ tp_init_thumb(struct tp_dispatch *tp) double w = 0.0, h = 0.0; struct device_coords edges; struct phys_coords mm = { 0.0, 0.0 }; - int threshold; + uint32_t threshold; + struct quirks_context *quirks; + struct quirks *q; if (!tp->buttons.is_clickpad) return; @@ -3095,11 +3091,13 @@ tp_init_thumb(struct tp_dispatch *tp) if (!abs) goto out; - threshold = tp_read_thumb_pressure_prop(tp, device); - if (threshold == 0) - goto out; - - tp->thumb.threshold = threshold; + quirks = evdev_libinput_context(device)->quirks; + q = quirks_fetch_for_device(quirks, device->udev_device); + if (quirks_get_uint32(q, + QUIRK_ATTR_THUMB_PRESSURE_THRESHOLD, + &threshold)) + tp->thumb.threshold = threshold; + quirks_unref(q); out: evdev_log_debug(device, @@ -3197,7 +3195,9 @@ tp_init_pressure(struct tp_dispatch *tp, { const struct input_absinfo *abs; unsigned int code; - const char *prop; + struct quirks_context *quirks; + struct quirks *q; + struct quirk_range r; int hi, lo; code = tp->has_mt ? ABS_MT_PRESSURE : ABS_PRESSURE; @@ -3209,20 +3209,16 @@ tp_init_pressure(struct tp_dispatch *tp, abs = libevdev_get_abs_info(device->evdev, code); assert(abs); - prop = udev_device_get_property_value(device->udev_device, - "LIBINPUT_ATTR_PRESSURE_RANGE"); - if (prop) { - if (!parse_range_property(prop, &hi, &lo)) { - evdev_log_bug_client(device, - "discarding invalid pressure range '%s'\n", - prop); - return; - } + quirks = evdev_libinput_context(device)->quirks; + q = quirks_fetch_for_device(quirks, device->udev_device); + if (q && quirks_get_range(q, QUIRK_ATTR_PRESSURE_RANGE, &r)) { + hi = r.upper; + lo = r.lower; if (hi == 0 && lo == 0) { evdev_log_info(device, "pressure-based touch detection disabled\n"); - return; + goto out; } } else { unsigned int range = abs->maximum - abs->minimum; @@ -3232,12 +3228,13 @@ tp_init_pressure(struct tp_dispatch *tp, lo = abs->minimum + 0.10 * range; } + if (hi > abs->maximum || hi < abs->minimum || lo > abs->maximum || lo < abs->minimum) { evdev_log_bug_libinput(device, "discarding out-of-bounds pressure range %d:%d\n", hi, lo); - return; + goto out; } tp->pressure.use_pressure = true; @@ -3248,14 +3245,19 @@ tp_init_pressure(struct tp_dispatch *tp, "using pressure-based touch detection (%d:%d)\n", lo, hi); +out: + quirks_unref(q); } static bool tp_init_touch_size(struct tp_dispatch *tp, struct evdev_device *device) { - const char *prop; + struct quirks_context *quirks; + struct quirks *q; + struct quirk_range r; int lo, hi; + int rc = false; if (!libevdev_has_event_code(device->evdev, EV_ABS, @@ -3263,28 +3265,25 @@ tp_init_touch_size(struct tp_dispatch *tp, return false; } - prop = udev_device_get_property_value(device->udev_device, - "LIBINPUT_ATTR_TOUCH_SIZE_RANGE"); - if (!prop) - return false; + quirks = evdev_libinput_context(device)->quirks; + q = quirks_fetch_for_device(quirks, device->udev_device); + if (q && quirks_get_range(q, QUIRK_ATTR_TOUCH_SIZE_RANGE, &r)) { + hi = r.upper; + lo = r.lower; + } else { + goto out; + } if (libevdev_get_num_slots(device->evdev) < 5) { evdev_log_bug_libinput(device, "Expected 5+ slots for touch size detection\n"); - return false; - } - - if (!parse_range_property(prop, &hi, &lo)) { - evdev_log_bug_client(device, - "discarding invalid touch size range '%s'\n", - prop); - return false; + goto out; } if (hi == 0 && lo == 0) { evdev_log_info(device, "touch size based touch detection disabled\n"); - return false; + goto out; } /* Thresholds apply for both major or minor */ @@ -3296,7 +3295,10 @@ tp_init_touch_size(struct tp_dispatch *tp, "using size-based touch detection (%d:%d)\n", hi, lo); - return true; + rc = true; +out: + quirks_unref(q); + return rc; } static int diff --git a/src/evdev.c b/src/evdev.c index e93dda6b..9fb11d83 100644 --- a/src/evdev.c +++ b/src/evdev.c @@ -43,6 +43,7 @@ #include "evdev.h" #include "filter.h" #include "libinput-private.h" +#include "quirks.h" #if HAVE_LIBWACOM #include @@ -410,7 +411,9 @@ static void evdev_tag_keyboard(struct evdev_device *device, struct udev_device *udev_device) { - const char *prop; + struct quirks_context *quirks; + struct quirks *q; + char *prop; int code; if (!libevdev_has_event_type(device->evdev, EV_KEY)) @@ -423,10 +426,9 @@ evdev_tag_keyboard(struct evdev_device *device, return; } - /* This should eventually become ID_INPUT_KEYBOARD_INTEGRATION */ - prop = udev_device_get_property_value(udev_device, - "LIBINPUT_ATTR_KEYBOARD_INTEGRATION"); - if (prop) { + quirks = evdev_libinput_context(device)->quirks; + q = quirks_fetch_for_device(quirks, device->udev_device); + if (q && quirks_get_string(q, QUIRK_ATTR_KEYBOARD_INTEGRATION, &prop)) { if (streq(prop, "internal")) { evdev_tag_keyboard_internal(device); } else if (streq(prop, "external")) { @@ -438,6 +440,8 @@ evdev_tag_keyboard(struct evdev_device *device, } } + quirks_unref(q); + device->tags |= EVDEV_TAG_KEYBOARD; } @@ -796,12 +800,16 @@ evdev_is_fake_mt_device(struct evdev_device *device) enum switch_reliability evdev_read_switch_reliability_prop(struct evdev_device *device) { - const char *prop; enum switch_reliability r; + struct quirks_context *quirks; + struct quirks *q; + char *prop; - prop = udev_device_get_property_value(device->udev_device, - "LIBINPUT_ATTR_LID_SWITCH_RELIABILITY"); - if (!parse_switch_reliability_property(prop, &r)) { + quirks = evdev_libinput_context(device)->quirks; + q = quirks_fetch_for_device(quirks, device->udev_device); + if (!q || !quirks_get_string(q, QUIRK_ATTR_LID_SWITCH_RELIABILITY, &prop)) { + r = RELIABILITY_UNKNOWN; + } else if (!parse_switch_reliability_property(prop, &r)) { evdev_log_error(device, "%s: switch reliability set to unknown value '%s'\n", device->devname, @@ -811,6 +819,8 @@ evdev_read_switch_reliability_prop(struct evdev_device *device) evdev_log_info(device, "will write switch open events\n"); } + quirks_unref(q); + return r; } @@ -1169,22 +1179,17 @@ evdev_read_wheel_tilt_props(struct evdev_device *device) static inline int evdev_get_trackpoint_range(struct evdev_device *device) { + struct quirks_context *quirks; + struct quirks *q; const char *prop; - int range = DEFAULT_TRACKPOINT_RANGE; + uint32_t range = DEFAULT_TRACKPOINT_RANGE; if (!(device->tags & EVDEV_TAG_TRACKPOINT)) return DEFAULT_TRACKPOINT_RANGE; - prop = udev_device_get_property_value(device->udev_device, - "LIBINPUT_ATTR_TRACKPOINT_RANGE"); - if (prop) { - if (!safe_atoi(prop, &range) || range < 0.0) { - evdev_log_error(device, - "trackpoint range property is present but invalid, " - "using %d instead\n", - DEFAULT_TRACKPOINT_RANGE); - range = DEFAULT_TRACKPOINT_RANGE; - } + quirks = evdev_libinput_context(device)->quirks; + q = quirks_fetch_for_device(quirks, device->udev_device); + if (q && quirks_get_uint32(q, QUIRK_ATTR_TRACKPOINT_RANGE, &range)) { goto out; } @@ -1215,6 +1220,8 @@ evdev_get_trackpoint_range(struct evdev_device *device) } out: + quirks_unref(q); + if (range == 0) { evdev_log_bug_libinput(device, "trackpoint range is zero\n"); range = DEFAULT_TRACKPOINT_RANGE; @@ -1256,12 +1263,11 @@ static inline uint32_t evdev_read_model_flags(struct evdev_device *device) { const struct model_map { - const char *property; + enum quirk quirk; enum evdev_device_model model; } model_map[] = { -#define MODEL(name) { "LIBINPUT_MODEL_" #name, EVDEV_MODEL_##name } +#define MODEL(name) { QUIRK_MODEL_##name, EVDEV_MODEL_##name } MODEL(LENOVO_X230), - MODEL(LENOVO_X220_TOUCHPAD_FW81), MODEL(CHROMEBOOK), MODEL(SYSTEM76_BONOBO), MODEL(SYSTEM76_GALAGO), @@ -1290,29 +1296,62 @@ evdev_read_model_flags(struct evdev_device *device) MODEL(LENOVO_CARBON_X1_6TH), MODEL(LENOVO_SCROLLPOINT), #undef MODEL - { "ID_INPUT_TRACKBALL", EVDEV_MODEL_TRACKBALL }, - { NULL, EVDEV_MODEL_DEFAULT }, + { 0, 0 }, }; const struct model_map *m = model_map; uint32_t model_flags = 0; uint32_t all_model_flags = 0; + struct quirks_context *quirks; + struct quirks *q; + + quirks = evdev_libinput_context(device)->quirks; + q = quirks_fetch_for_device(quirks, device->udev_device); + + while (q && m->quirk) { + bool is_set; - while (m->property) { /* Check for flag re-use */ - if (strneq("LIBINPUT_MODEL_", m->property, 15)) { - assert((all_model_flags & m->model) == 0); - all_model_flags |= m->model; + assert((all_model_flags & m->model) == 0); + all_model_flags |= m->model; + + if (quirks_get_bool(q, m->quirk, &is_set)) { + if (is_set) { + evdev_log_debug(device, + "tagged as %s\n", + quirk_get_name(m->quirk)); + model_flags |= m->model; + } else { + evdev_log_debug(device, + "untagged as %s\n", + quirk_get_name(m->quirk)); + model_flags &= ~m->model; + } } - if (parse_udev_flag(device, - device->udev_device, - m->property)) { - evdev_log_debug(device, "tagged as %s\n", m->property); - model_flags |= m->model; - } m++; } + quirks_unref(q); + + if (parse_udev_flag(device, + device->udev_device, + "ID_INPUT_TRACKBALL")) { + evdev_log_debug(device, "tagged as trackball\n"); + model_flags |= EVDEV_MODEL_TRACKBALL; + } + + /** + * Device is 6 years old at the time of writing this and this was + * one of the few udev properties that wasn't reserved for private + * usage, so we need to keep this for backwards compat. + */ + if (parse_udev_flag(device, + device->udev_device, + "LIBINPUT_MODEL_LENOVO_X220_TOUCHPAD_FW81")) { + evdev_log_debug(device, "tagged as trackball\n"); + model_flags |= EVDEV_MODEL_LENOVO_X220_TOUCHPAD_FW81; + } + return model_flags; } @@ -1321,16 +1360,25 @@ evdev_read_attr_res_prop(struct evdev_device *device, size_t *xres, size_t *yres) { - struct udev_device *udev; - const char *res_prop; + struct quirks_context *quirks; + struct quirks *q; + struct quirk_dimensions dim; + bool rc = false; - udev = device->udev_device; - res_prop = udev_device_get_property_value(udev, - "LIBINPUT_ATTR_RESOLUTION_HINT"); - if (!res_prop) + quirks = evdev_libinput_context(device)->quirks; + q = quirks_fetch_for_device(quirks, device->udev_device); + if (!q) return false; - return parse_dimension_property(res_prop, xres, yres); + rc = quirks_get_dimensions(q, QUIRK_ATTR_RESOLUTION_HINT, &dim); + if (rc) { + *xres = dim.x; + *yres = dim.y; + } + + quirks_unref(q); + + return rc; } static inline bool @@ -1338,16 +1386,25 @@ evdev_read_attr_size_prop(struct evdev_device *device, size_t *size_x, size_t *size_y) { - struct udev_device *udev; - const char *size_prop; + struct quirks_context *quirks; + struct quirks *q; + struct quirk_dimensions dim; + bool rc = false; - udev = device->udev_device; - size_prop = udev_device_get_property_value(udev, - "LIBINPUT_ATTR_SIZE_HINT"); - if (!size_prop) + quirks = evdev_libinput_context(device)->quirks; + q = quirks_fetch_for_device(quirks, device->udev_device); + if (!q) return false; - return parse_dimension_property(size_prop, size_x, size_y); + rc = quirks_get_dimensions(q, QUIRK_ATTR_SIZE_HINT, &dim); + if (rc) { + *size_x = dim.x; + *size_y = dim.y; + } + + quirks_unref(q); + + return rc; } /* Return 1 if the device is set to the fake resolution or 0 otherwise */ diff --git a/test/litest-device-alps-dualpoint.c b/test/litest-device-alps-dualpoint.c index 7e68ec61..b9943001 100644 --- a/test/litest-device-alps-dualpoint.c +++ b/test/litest-device-alps-dualpoint.c @@ -103,15 +103,10 @@ static struct input_absinfo absinfo[] = { { .value = -1 } }; -static const char udev_rule[] = -"ACTION==\"remove\", GOTO=\"touchpad_end\"\n" -"KERNEL!=\"event*\", GOTO=\"touchpad_end\"\n" -"ENV{ID_INPUT_TOUCHPAD}==\"\", GOTO=\"touchpad_end\"\n" -"\n" -"ATTRS{name}==\"litest AlpsPS/2 ALPS DualPoint TouchPad\"," -" ENV{LIBINPUT_MODEL_TOUCHPAD_VISIBLE_MARKER}=\"1\"\n" -"\n" -"LABEL=\"touchpad_end\""; +static const char quirk_file[] = +"[litest ALPS Touchpad]\n" +"MatchName=litest AlpsPS/2 ALPS DualPoint TouchPad\n" +"ModelTouchpadVisibleMarker=1\n"; TEST_DEVICE("alps-dualpoint", .type = LITEST_ALPS_DUALPOINT, @@ -122,5 +117,5 @@ TEST_DEVICE("alps-dualpoint", .id = &input_id, .events = events, .absinfo = absinfo, - .udev_rule = udev_rule, + .quirk_file = quirk_file, ) diff --git a/test/litest-device-apple-appletouch.c b/test/litest-device-apple-appletouch.c index fa13e8e5..c7a9e4f1 100644 --- a/test/litest-device-apple-appletouch.c +++ b/test/litest-device-apple-appletouch.c @@ -83,15 +83,10 @@ static struct input_absinfo absinfo[] = { { .value = -1 } }; -static const char udev_rule[] = -"ACTION==\"remove\", GOTO=\"touchpad_end\"\n" -"KERNEL!=\"event*\", GOTO=\"touchpad_end\"\n" -"ENV{ID_INPUT_TOUCHPAD}==\"\", GOTO=\"touchpad_end\"\n" -"\n" -"ATTRS{name}==\"litest appletouch\"," -" ENV{LIBINPUT_MODEL_APPLE_TOUCHPAD_ONEBUTTON}=\"1\"\n" -"\n" -"LABEL=\"touchpad_end\""; +static const char quirk_file[] = +"[litest ALPS Touchpad]\n" +"MatchName=litest appletouch\n" +"ModelAppleTouchpadOneButton=1\n"; TEST_DEVICE("appletouch", .type = LITEST_APPLETOUCH, @@ -102,5 +97,5 @@ TEST_DEVICE("appletouch", .id = &input_id, .events = events, .absinfo = absinfo, - .udev_rule = udev_rule, + .quirk_file = quirk_file, ) diff --git a/test/litest-device-gpio-keys.c b/test/litest-device-gpio-keys.c index 67993912..ee226de1 100644 --- a/test/litest-device-gpio-keys.c +++ b/test/litest-device-gpio-keys.c @@ -47,11 +47,15 @@ static const char udev_rule[] = "KERNEL!=\"event*\", GOTO=\"switch_end\"\n" "\n" "ATTRS{name}==\"litest gpio-keys*\",\\\n" -" ENV{ID_INPUT_SWITCH}=\"1\",\\\n" -" ENV{LIBINPUT_ATTR_LID_SWITCH_RELIABILITY}=\"reliable\"\n" +" ENV{ID_INPUT_SWITCH}=\"1\"\n" "\n" "LABEL=\"switch_end\""; +static const char quirk_file[] = +"[litest gpio quirk]\n" +"MatchName=litest gpio-keys\n" +"AttrLidSwitchReliability=reliable\n"; + TEST_DEVICE("gpio-keys", .type = LITEST_GPIO_KEYS, .features = LITEST_SWITCH, @@ -63,4 +67,5 @@ TEST_DEVICE("gpio-keys", .absinfo = NULL, .udev_rule = udev_rule, + .quirk_file = quirk_file, ) diff --git a/test/litest-device-huion-pentablet.c b/test/litest-device-huion-pentablet.c index 053212b9..26b8adfb 100644 --- a/test/litest-device-huion-pentablet.c +++ b/test/litest-device-huion-pentablet.c @@ -88,15 +88,10 @@ static int events[] = { -1, -1, }; -static const char udev_rule[] = -"ACTION==\"remove\", GOTO=\"huion_end\"\n" -"KERNEL!=\"event*\", GOTO=\"huion_end\"\n" -"ENV{ID_INPUT_TABLET}==\"\", GOTO=\"huion_end\"\n" -"\n" -"ATTRS{name}==\"litest HUION PenTablet Pen\"," -" ENV{LIBINPUT_MODEL_TABLET_NO_PROXIMITY_OUT}=\"1\"\n" -"\n" -"LABEL=\"huion_end\""; +static const char quirk_file[] = +"[litest HUION tablet]\n" +"MatchName=litest HUION PenTablet Pen\n" +"ModelTabletNoProximityOut=1\n"; TEST_DEVICE("huion-tablet", .type = LITEST_HUION_TABLET, @@ -107,5 +102,5 @@ TEST_DEVICE("huion-tablet", .id = &input_id, .events = events, .absinfo = absinfo, - .udev_rule = udev_rule, + .quirk_file = quirk_file, ) diff --git a/test/litest-device-lid-switch-surface3.c b/test/litest-device-lid-switch-surface3.c index a0df1696..8d72b672 100644 --- a/test/litest-device-lid-switch-surface3.c +++ b/test/litest-device-lid-switch-surface3.c @@ -43,11 +43,15 @@ static const char udev_rule[] = "KERNEL!=\"event*\", GOTO=\"switch_end\"\n" "\n" "ATTRS{name}==\"litest Lid Switch Surface3*\",\\\n" -" ENV{ID_INPUT_SWITCH}=\"1\",\\\n" -" ENV{LIBINPUT_ATTR_LID_SWITCH_RELIABILITY}=\"write_open\"\n" +" ENV{ID_INPUT_SWITCH}=\"1\"\n" "\n" "LABEL=\"switch_end\""; +static const char quirk_file[] = +"[litest Surface Lid]\n" +"MatchName=litest Lid Switch Surface3\n" +"AttrLidSwitchReliability=write_open\n"; + TEST_DEVICE("lid-switch-surface3", .type = LITEST_LID_SWITCH_SURFACE3, .features = LITEST_SWITCH, @@ -59,4 +63,5 @@ TEST_DEVICE("lid-switch-surface3", .absinfo = NULL, .udev_rule = udev_rule, + .quirk_file = quirk_file, ) diff --git a/test/litest-device-lid-switch.c b/test/litest-device-lid-switch.c index 5c141cb3..e7b4e7c9 100644 --- a/test/litest-device-lid-switch.c +++ b/test/litest-device-lid-switch.c @@ -42,11 +42,15 @@ static const char udev_rule[] = "KERNEL!=\"event*\", GOTO=\"switch_end\"\n" "\n" "ATTRS{name}==\"litest Lid Switch\",\\\n" -" ENV{ID_INPUT_SWITCH}=\"1\",\\\n" -" ENV{LIBINPUT_ATTR_LID_SWITCH_RELIABILITY}=\"reliable\"\n" +" ENV{ID_INPUT_SWITCH}=\"1\"\n" "\n" "LABEL=\"switch_end\""; +static const char quirk_file[] = +"[litest Lid Switch]\n" +"MatchName=litest Lid Switch\n" +"AttrLidSwitchReliability=reliable\n"; + TEST_DEVICE("lid-switch", .type = LITEST_LID_SWITCH, .features = LITEST_SWITCH, @@ -58,4 +62,5 @@ TEST_DEVICE("lid-switch", .absinfo = NULL, .udev_rule = udev_rule, + .quirk_file = quirk_file, ) diff --git a/test/litest-device-synaptics-i2c.c b/test/litest-device-synaptics-i2c.c index d32e0a50..ce2d905b 100644 --- a/test/litest-device-synaptics-i2c.c +++ b/test/litest-device-synaptics-i2c.c @@ -79,15 +79,10 @@ static struct input_absinfo absinfo[] = { { .value = -1 } }; -static const char udev_rule[] = -"ACTION==\"remove\", GOTO=\"touchpad_end\"\n" -"KERNEL!=\"event*\", GOTO=\"touchpad_end\"\n" -"ENV{ID_INPUT_TOUCHPAD}==\"\", GOTO=\"touchpad_end\"\n" -"\n" -"ATTRS{name}==\"litest DLL0704:01 06CB:76AD Touchpad\"," -" ENV{LIBINPUT_MODEL_TOUCHPAD_VISIBLE_MARKER}=\"1\"\n" -"\n" -"LABEL=\"touchpad_end\""; +static const char quirk_file[] = +"[litest Synaptics i2c Touchpad]\n" +"MatchName=litest DLL0704:01 06CB:76AD Touchpad\n" +"ModelTouchpadVisibleMarker=1\n"; TEST_DEVICE("synaptics-i2c", .type = LITEST_SYNAPTICS_I2C, @@ -98,5 +93,5 @@ TEST_DEVICE("synaptics-i2c", .id = &input_id, .events = events, .absinfo = absinfo, - .udev_rule = udev_rule, + .quirk_file = quirk_file, ) diff --git a/test/litest-device-synaptics-x1-carbon-3rd.c b/test/litest-device-synaptics-x1-carbon-3rd.c index 73fac59a..b3efe47a 100644 --- a/test/litest-device-synaptics-x1-carbon-3rd.c +++ b/test/litest-device-synaptics-x1-carbon-3rd.c @@ -105,15 +105,10 @@ static struct input_absinfo absinfo[] = { { .value = -1 } }; -static const char udev_rule[] = -"ACTION==\"remove\", GOTO=\"touchpad_end\"\n" -"KERNEL!=\"event*\", GOTO=\"touchpad_end\"\n" -"ENV{ID_INPUT_TOUCHPAD}==\"\", GOTO=\"touchpad_end\"\n" -"\n" -"ATTRS{name}==\"litest SynPS/2 Synaptics TouchPad X1C3rd\"," -" ENV{LIBINPUT_MODEL_LENOVO_T450_TOUCHPAD}=\"1\"\n" -"\n" -"LABEL=\"touchpad_end\""; +static const char quirk_file[] = +"[litest Synaptics X1 Carbon 3rd Touchpad]\n" +"MatchName=litest SynPS/2 Synaptics TouchPad X1C3rd\n" +"ModelLenovoT450Touchpad=1\n"; TEST_DEVICE("synaptics-carbon3rd", .type = LITEST_SYNAPTICS_TRACKPOINT_BUTTONS, @@ -124,5 +119,5 @@ TEST_DEVICE("synaptics-carbon3rd", .id = &input_id, .events = events, .absinfo = absinfo, - .udev_rule = udev_rule, + .quirk_file = quirk_file, ) diff --git a/test/litest-device-waltop-tablet.c b/test/litest-device-waltop-tablet.c index 96052e27..7aa69d99 100644 --- a/test/litest-device-waltop-tablet.c +++ b/test/litest-device-waltop-tablet.c @@ -220,15 +220,10 @@ static int events[] = { -1, -1, }; -static const char udev_rule[] = -"ACTION==\"remove\", GOTO=\"waltop_end\"\n" -"KERNEL!=\"event*\", GOTO=\"waltop_end\"\n" -"ENV{ID_INPUT_TABLET}==\"\", GOTO=\"waltop_end\"\n" -"\n" -"ATTRS{name}==\"litest WALTOP Batteryless Tablet*\",\\\n" -" ENV{LIBINPUT_ATTR_SIZE_HINT}=\"200x200\"\n" -"\n" -"LABEL=\"waltop_end\""; +static const char quirk_file[] = +"[litest Waltop Tablet]\n" +"MatchName=litest WALTOP Batteryless Tablet*\n" +"AttrSizeHint=200x200\n"; TEST_DEVICE("waltop-tablet", .type = LITEST_WALTOP, @@ -239,5 +234,5 @@ TEST_DEVICE("waltop-tablet", .id = &input_id, .events = events, .absinfo = absinfo, - .udev_rule = udev_rule, + .quirk_file = quirk_file, ) diff --git a/test/test-switch.c b/test/test-switch.c index 74cb43e0..043a8e3a 100644 --- a/test/test-switch.c +++ b/test/test-switch.c @@ -143,16 +143,15 @@ END_TEST static bool lid_switch_is_reliable(struct litest_device *dev) { - struct udev_device *udev_device; - const char *prop; + char *prop; bool is_reliable = false; - udev_device = libinput_device_get_udev_device(dev->libinput_device); - prop = udev_device_get_property_value(udev_device, - "LIBINPUT_ATTR_LID_SWITCH_RELIABILITY"); + if (quirks_get_string(dev->quirks, + QUIRK_ATTR_LID_SWITCH_RELIABILITY, + &prop)) { + is_reliable = streq(prop, "reliable"); + } - is_reliable = prop && streq(prop, "reliable"); - udev_device_unref(udev_device); return is_reliable; } diff --git a/test/test-tablet.c b/test/test-tablet.c index 92c52ae8..1638b20d 100644 --- a/test/test-tablet.c +++ b/test/test-tablet.c @@ -293,19 +293,13 @@ END_TEST static inline bool tablet_has_proxout_quirk(struct litest_device *dev) { - struct udev_device *udev_device; - bool has_quirk; + bool is_set = false; + if (!quirks_get_bool(dev->quirks, + QUIRK_MODEL_TABLET_NO_PROXIMITY_OUT, + &is_set)) + return false; - udev_device = libinput_device_get_udev_device(dev->libinput_device); - - has_quirk = !!udev_device_get_property_value(udev_device, - "LIBINPUT_MODEL_TABLET_NO_PROXIMITY_OUT"); - if (!has_quirk) - has_quirk = !libevdev_has_event_code(dev->evdev, EV_KEY, BTN_TOOL_PEN); - - udev_device_unref(udev_device); - - return has_quirk; + return is_set; } START_TEST(tip_up_prox_out) From b926497cc6efc87d89bb05b5d44640822fbdb21b Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Mon, 4 Jun 2018 11:18:02 +1000 Subject: [PATCH 13/14] quirks: add the devicetree implementation Using the compatible string Signed-off-by: Peter Hutterer --- src/quirks.c | 68 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 53 insertions(+), 15 deletions(-) diff --git a/src/quirks.c b/src/quirks.c index 1a44574a..2fca275a 100644 --- a/src/quirks.c +++ b/src/quirks.c @@ -127,7 +127,7 @@ struct match { /* We can have more than one type set, so this is a bitfield */ uint32_t udev_type; - char *dt; /* FIXME: clarify */ + char *dt; /* device tree compatible (first) string */ }; /** @@ -168,6 +168,7 @@ struct quirks_context { struct libinput *libinput; /* for logging */ char *dmi; + char *dt; struct list sections; @@ -367,6 +368,35 @@ init_dmi(void) return copy; } +/** + * Return the dt compatible string + */ +static inline char * +init_dt(void) +{ + char compatible[1024]; + char *copy = NULL; + const char *syspath = "/sys/firmware/devicetree/base/compatible"; + FILE *fp; + + if (getenv("LIBINPUT_RUNNING_TEST_SUITE")) + return safe_strdup(""); + + fp = fopen(syspath, "r"); + if (!fp) + return NULL; + + /* devicetree/base/compatible has multiple null-terminated entries + but we only care about the first one here, so strdup is enough */ + if (fgets(compatible, sizeof(compatible), fp)) { + copy = safe_strdup(compatible); + } + + fclose(fp); + + return copy; +} + static inline struct section * section_new(const char *path, const char *name) { @@ -487,7 +517,8 @@ parse_match(struct quirks_context *ctx, else goto out; } else if (streq(key, "MatchDeviceTree")) { - /* FIXME */ + check_set_bit(s, M_DT); + s->match.dt = safe_strdup(value); } else { qlog_error(ctx, "Unknown match key '%s'\n", key); goto out; @@ -966,7 +997,8 @@ quirks_init_subsystem(const char *data_path, qlog_debug(ctx, "%s is data root\n", data_path); ctx->dmi = init_dmi(); - if (!ctx->dmi) + ctx->dt = init_dt(); + if (!ctx->dmi && !ctx->dt) goto error; if (!parse_files(ctx, data_path)) @@ -1160,32 +1192,37 @@ match_fill_udev_type(struct match *m, } static inline void -match_fill_dmi(struct match *m, - char *dmi) +match_fill_dmi_dt(struct match *m, char *dmi, char *dt) { - m->dmi = dmi; - m->bits |= M_DMI; + if (dmi) { + m->dmi = dmi; + m->bits |= M_DMI; + } + + if (dt) { + m->dt = dt; + m->bits |= M_DT; + } } static struct match * match_new(struct udev_device *device, - char *dmi) + char *dmi, char *dt) { struct match *m = zalloc(sizeof *m); match_fill_name(m, device); match_fill_bus_vid_pid(m, device); - match_fill_dmi(m, dmi); + match_fill_dmi_dt(m, dmi, dt); match_fill_udev_type(m, device); - /* FIXME: dt */ return m; } static void match_free(struct match *m) { + /* dmi and dt are global */ free(m->name); - free(m->dt); free(m); } @@ -1262,13 +1299,14 @@ quirk_match_section(struct quirks_context *ctx, if (fnmatch(s->match.dmi, m->dmi, 0) == 0) matched_flags |= flag; break; + case M_DT: + if (fnmatch(s->match.dt, m->dt, 0) == 0) + matched_flags |= flag; + break; case M_UDEV_TYPE: if (s->match.udev_type & m->udev_type) matched_flags |= flag; break; - case M_DT: - /* FIXME */ - break; default: abort(); } @@ -1305,7 +1343,7 @@ quirks_fetch_for_device(struct quirks_context *ctx, q = quirks_new(); - m = match_new(udev_device, ctx->dmi); + m = match_new(udev_device, ctx->dmi, ctx->dt); list_for_each(s, &ctx->sections, link) { quirk_match_section(ctx, q, s, m, udev_device); From d0fa740ad917f462dcd8e2fbe2979be12311fdf6 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Wed, 6 Jun 2018 14:39:39 +1000 Subject: [PATCH 14/14] Hook up list-quirks --validate for the test suite All the tests fill fail anyway if the validation fails but this is a quick way to fail everything early. Signed-off-by: Peter Hutterer --- meson.build | 18 +++++++++++------- tools/libinput-list-quirks.c | 24 ++++++++++++++++++++---- tools/libinput-list-quirks.man | 10 +++++++++- 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/meson.build b/meson.build index 5e4db287..8ac75cc7 100644 --- a/meson.build +++ b/meson.build @@ -489,13 +489,17 @@ configure_file(input : 'tools/libinput-debug-events.man', ) libinput_list_quirks_sources = [ 'tools/libinput-list-quirks.c' ] -executable('libinput-list-quirks', - libinput_list_quirks_sources, - dependencies : [dep_libquirks, dep_libinput], - include_directories : [includes_src, includes_include], - install_dir : libinput_tool_path, - install : true - ) +libinput_list_quirks = executable('libinput-list-quirks', + libinput_list_quirks_sources, + dependencies : [dep_libquirks, dep_libinput], + include_directories : [includes_src, includes_include], + install_dir : libinput_tool_path, + install : true + ) +test('validate-quirks', + libinput_list_quirks, + args: ['--validate-only', '--data-dir=@0@'.format(join_paths(meson.source_root(), 'data'))] + ) configure_file(input : 'tools/libinput-list-quirks.man', output : 'libinput-list-quirks.1', diff --git a/tools/libinput-list-quirks.c b/tools/libinput-list-quirks.c index 73e84895..009f7d64 100644 --- a/tools/libinput-list-quirks.c +++ b/tools/libinput-list-quirks.c @@ -136,21 +136,27 @@ list_device_quirks(struct quirks_context *ctx, struct udev_device *device) static void usage(void) { - printf("Usage: %s [--data-dir /path/to/data/dir] /dev/input/event0\n", + printf("Usage:\n" + " %s [--data-dir /path/to/data/dir] /dev/input/event0\n" + " Print the quirks for the given device\n" + "\n", + program_invocation_short_name); + printf(" %s [--data-dir /path/to/data/dir] --validate-only\n" + " Validate the database\n", program_invocation_short_name); - printf("Note: this tool also takes a syspath\n"); } int main(int argc, char **argv) { - struct udev *udev; + struct udev *udev = NULL; struct udev_device *device = NULL; const char *path; const char *data_path = NULL, *override_file = NULL; int rc = 1; struct quirks_context *quirks; + bool validate = false; while (1) { int c; @@ -158,11 +164,13 @@ main(int argc, char **argv) enum { OPT_VERBOSE, OPT_DATADIR, + OPT_VALIDATE, }; static struct option opts[] = { { "help", no_argument, 0, 'h' }, { "verbose", no_argument, 0, OPT_VERBOSE }, { "data-dir", required_argument, 0, OPT_DATADIR }, + { "validate-only", no_argument, 0, OPT_VALIDATE }, { 0, 0, 0, 0} }; @@ -184,13 +192,16 @@ main(int argc, char **argv) case OPT_DATADIR: data_path = optarg; break; + case OPT_VALIDATE: + validate = true; + break; default: usage(); return 1; } } - if (optind >= argc) { + if (optind >= argc && !validate) { usage(); return 1; } @@ -214,6 +225,11 @@ main(int argc, char **argv) return 1; } + if (validate) { + rc = 0; + goto out; + } + udev = udev_new(); path = argv[optind]; if (strneq(path, "/sys/", 5)) { diff --git a/tools/libinput-list-quirks.man b/tools/libinput-list-quirks.man index 9d90847e..e18495f3 100644 --- a/tools/libinput-list-quirks.man +++ b/tools/libinput-list-quirks.man @@ -2,7 +2,11 @@ .SH NAME libinput\-list\-quirks \- quirk debug helper for libinput .SH SYNOPSIS -.B libinput list\-quirks [\-\-help] [\-\-data\-dir /path/to/dir] [\-\-verbose\fB] \fI/dev/input/event0\fB +.B libinput list\-quirks [\-\-data\-dir /path/to/dir] [\-\-verbose\fB] \fI/dev/input/event0\fB +.br +.B libinput list\-quirks [\-\-data\-dir /path/to/dir] [\-\-verbose\fB] \-\-validate\-only +.br +.B libinput list\-quirks [\-\-help] .SH DESCRIPTION .PP The @@ -20,6 +24,10 @@ Use the given directory as data directory for quirks files. .B \-\-help Print help .TP 8 +.B \-\-validate\-only +Only validate that the quirks files can be parsed. When this option is +given, no device file should be supplied. +.TP 8 .B \-\-verbose Use verbose output, useful for debugging. .SH LIBINPUT