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..55b9056f --- /dev/null +++ b/data/README.md @@ -0,0 +1,77 @@ += 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 +* 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..8ac75cc7 100644 --- a/meson.build +++ b/meson.build @@ -181,6 +181,57 @@ 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', +] + +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 = [ + '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 +271,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 +369,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 +488,26 @@ 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' ] +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', + 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 +770,7 @@ if get_option('tests') dep_dl, dep_lm, dep_libsystemd, + dep_libquirks, ] configure_file(input : 'udev/80-libinput-test-device.rules', @@ -763,7 +837,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/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index a78a6151..1c710a5d 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) @@ -2841,16 +2842,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 @@ -2917,19 +2927,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 @@ -2953,24 +2964,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 @@ -3045,25 +3058,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) { @@ -3072,7 +3066,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; @@ -3101,11 +3097,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, @@ -3203,7 +3201,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; @@ -3215,20 +3215,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; @@ -3238,12 +3234,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; @@ -3254,14 +3251,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, @@ -3269,28 +3271,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 */ @@ -3302,7 +3301,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/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-util.c b/src/libinput-util.c index 3198220d..bb6a9c32 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 574a8cf8..8c67dcbd 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); @@ -518,6 +519,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/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/quirks.c b/src/quirks.c new file mode 100644 index 00000000..2fca275a --- /dev/null +++ b/src/quirks.c @@ -0,0 +1,1494 @@ +/* + * 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; /* dmi modalias with preceding "dmi:" */ + + /* We can have more than one type set, so this is a bitfield */ + uint32_t udev_type; + + char *dt; /* device tree compatible (first) string */ +}; + +/** + * 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; + char *dt; + + 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 = 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; + + 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; +} + +/** + * 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) +{ + 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")) { + check_set_bit(s, M_DT); + s->match.dt = safe_strdup(value); + } 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)) { + 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; + + /* 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(); + ctx->dt = init_dt(); + if (!ctx->dmi && !ctx->dt) + 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_dt(struct match *m, char *dmi, char *dt) +{ + 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 *dt) +{ + struct match *m = zalloc(sizeof *m); + + match_fill_name(m, device); + match_fill_bus_vid_pid(m, device); + match_fill_dmi_dt(m, dmi, dt); + match_fill_udev_type(m, device); + return m; +} + +static void +match_free(struct match *m) +{ + /* dmi and dt are global */ + free(m->name); + 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_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; + 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, ctx->dt); + + 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/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; 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/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 b72ec444..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; @@ -87,6 +89,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 @@ -741,6 +744,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 */ } @@ -788,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) { @@ -803,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); @@ -887,6 +910,8 @@ out: free(n); } + quirks_context_unref(quirks_context); + return failed; } @@ -998,6 +1023,7 @@ litest_run(int argc, char **argv) { int failed = 0; int inhibit_lock_fd; + char *quirks_dir; list_init(&created_files_list); @@ -1011,6 +1037,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); @@ -1104,13 +1134,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, @@ -1169,6 +1206,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) { @@ -1212,6 +1329,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); } @@ -1399,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); @@ -1522,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; 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-misc.c b/test/test-misc.c index 50fe018f..e07f233c 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; @@ -1583,6 +1695,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); @@ -1617,6 +1790,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); @@ -1626,4 +1802,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); } diff --git a/test/test-quirks.c b/test/test-quirks.c new file mode 100644 index 00000000..16bbacf0 --- /dev/null +++ b/test/test-quirks.c @@ -0,0 +1,956 @@ +/* + * 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_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; + 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_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; + 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 + +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); + 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_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); + 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); + + 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); +} 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) diff --git a/tools/libinput-list-quirks.c b/tools/libinput-list-quirks.c new file mode 100644 index 00000000..009f7d64 --- /dev/null +++ b/tools/libinput-list-quirks.c @@ -0,0 +1,261 @@ +/* + * 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:\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); +} + +int +main(int argc, char **argv) +{ + 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; + int option_index = 0; + 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} + }; + + 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; + case OPT_VALIDATE: + validate = true; + break; + default: + usage(); + return 1; + } + } + + if (optind >= argc && !validate) { + 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; + } + + if (validate) { + rc = 0; + goto out; + } + + 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..e18495f3 --- /dev/null +++ b/tools/libinput-list-quirks.man @@ -0,0 +1,36 @@ +.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 [\-\-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 +.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 \-\-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 +Part of the +.B libinput(1) +suite