diff options
32 files changed, 3512 insertions, 2 deletions
diff --git a/data/10-generic-keyboard.quirks b/data/10-generic-keyboard.quirks new file mode 100644 index 00000000..3063dad6 --- /dev/null +++ b/data/10-generic-keyboard.quirks @@ -0,0 +1,4 @@ +[Serial Keyboards] +MatchUdevType=keyboard +MatchBus=ps2 +AttrKeyboardIntegration=internal diff --git a/data/10-generic-lid.quirks b/data/10-generic-lid.quirks new file mode 100644 index 00000000..f3748e16 --- /dev/null +++ b/data/10-generic-lid.quirks @@ -0,0 +1,9 @@ +[Lid Switch Ct9] +MatchName=*Lid Switch* +MatchDMIModalias=dmi:*:ct9:* +AttrLidSwitchReliability=reliable + +[Lid Switch Ct10] +MatchName=*Lid Switch* +MatchDMIModalias=dmi:*:ct10:* +AttrLidSwitchReliability=reliable diff --git a/data/10-generic-trackball.quirks b/data/10-generic-trackball.quirks new file mode 100644 index 00000000..a554a44d --- /dev/null +++ b/data/10-generic-trackball.quirks @@ -0,0 +1,3 @@ +[Trackball] +MatchName=*Trackball* +ModelTrackball=1 diff --git a/data/30-vendor-aiptek.quirks b/data/30-vendor-aiptek.quirks new file mode 100644 index 00000000..24abb134 --- /dev/null +++ b/data/30-vendor-aiptek.quirks @@ -0,0 +1,5 @@ +[Aiptek No Tilt Tablet] +MatchUdevType=tablet +MatchBus=usb +MatchVendor=0x08CA +ModelTabletNoTilt=1 diff --git a/data/30-vendor-alps.quirks b/data/30-vendor-alps.quirks new file mode 100644 index 00000000..706a3f7a --- /dev/null +++ b/data/30-vendor-alps.quirks @@ -0,0 +1,9 @@ +[AlpsTouchpadDualPoint] +MatchUdevType=touchpad +MatchName=*AlpsPS/2 ALPS DualPoint TouchPad +ModelALPSTouchpad=1 + +[AlpsTouchpadGlidePoint] +MatchUdevType=touchpad +MatchName=*AlpsPS/2 ALPS GlidePoint +ModelALPSTouchpad=1 diff --git a/data/30-vendor-cyapa.quirks b/data/30-vendor-cyapa.quirks new file mode 100644 index 00000000..b58a5541 --- /dev/null +++ b/data/30-vendor-cyapa.quirks @@ -0,0 +1,3 @@ +[Cyapa Touchpads] +MatchName=*Cypress APA Trackpad ?cyapa? +AttrPressureRange=10:8 diff --git a/data/30-vendor-elantech.quirks b/data/30-vendor-elantech.quirks new file mode 100644 index 00000000..59a8157f --- /dev/null +++ b/data/30-vendor-elantech.quirks @@ -0,0 +1,4 @@ +[Elantech Touchpads] +MatchName=*Elantech Touchpad* +AttrResolutionHint=31x31 +AttrPressureRange=10:8 diff --git a/data/30-vendor-huion.quirks b/data/30-vendor-huion.quirks new file mode 100644 index 00000000..65f3b2c1 --- /dev/null +++ b/data/30-vendor-huion.quirks @@ -0,0 +1,12 @@ +# HUION PenTablet device. Some of these devices send a BTN_TOOL_PEN event +# with value 1 on the first event received by the device but never send the +# matching BTN_TOOL_PEN value 0 event. The device appears as if it was +# permanently in proximity. +# +# HUION re-uses USB IDs for its devices, not every HUION tablet is +# affected by this bug, libinput will auto-disable this feature +[HUION PenTablet] +MatchUdevType=tablet +MatchBus=usb +MatchVendor=0x256C +ModelTabletNoProximityOut=1 diff --git a/data/30-vendor-ibm.quirks b/data/30-vendor-ibm.quirks new file mode 100644 index 00000000..195fc21a --- /dev/null +++ b/data/30-vendor-ibm.quirks @@ -0,0 +1,39 @@ +# IBM/Lenovo Scrollpoint mouse. Instead of a scroll wheel these mice +# feature trackpoint-like sticks which generate a huge amount of scroll +# events that need to be handled differently than scroll wheel events + +[IBM ScrollPoint Mouse 3100] +MatchUdevType=mouse +MatchVendor=0x04B3 +MatchProduct=0x3100 +ModelLenovoScrollPoint=1 + +[IBM ScrollPoint Mouse 3103] +MatchUdevType=mouse +MatchVendor=0x04B3 +MatchProduct=0x3103 +ModelLenovoScrollPoint=1 + +[IBM ScrollPoint Mouse 3105] +MatchUdevType=mouse +MatchVendor=0x04B3 +MatchProduct=0x3105 +ModelLenovoScrollPoint=1 + +[IBM ScrollPoint Mouse 3108] +MatchUdevType=mouse +MatchVendor=0x04B3 +MatchProduct=0x3108 +ModelLenovoScrollPoint=1 + +[IBM ScrollPoint Mouse 3109] +MatchUdevType=mouse +MatchVendor=0x04B3 +MatchProduct=0x3109 +ModelLenovoScrollPoint=1 + +[IBM ScrollPoint Mouse 6049] +MatchUdevType=mouse +MatchVendor=0x17EF +MatchProduct=0x6049 +ModelLenovoScrollPoint=1 diff --git a/data/30-vendor-logitech.quirks b/data/30-vendor-logitech.quirks new file mode 100644 index 00000000..ebbc9cc2 --- /dev/null +++ b/data/30-vendor-logitech.quirks @@ -0,0 +1,44 @@ +[Logitech M570] +MatchName=*Logitech M570* +ModelTrackball=1 + +[Logitech Marble Mouse Trackball] +MatchUdevType=mouse +MatchBus=usb +MatchVendor=0x46D +MatchProduct=0xC408 +ModelLogitechMarbleMouse=1 + +[Logitech K400] +MatchUdevType=mouse +MatchBus=usb +MatchVendor=0x046D +MatchProduct=0x4024 +ModelBouncingKeys=1 + +[Logitech K400r] +MatchUdevType=mouse +MatchBus=usb +MatchVendor=0x046D +MatchProduct=0x404B +ModelBouncingKeys=1 + +[Logitech K830] +MatchUdevType=mouse +MatchBus=usb +MatchVendor=0x046D +MatchProduct=0x404C +ModelBouncingKeys=1 + +[Logitech K400Plus] +MatchUdevType=mouse +MatchBus=usb +MatchVendor=0x046D +MatchProduct=0x404D +ModelBouncingKeys=1 + +[Logitech Wireless Touchpad] +MatchBus=usb +MatchVendor=0x046D +MatchProduct=0x4011 +AttrPalmPressureThreshold=400 diff --git a/data/30-vendor-microsoft.quirks b/data/30-vendor-microsoft.quirks new file mode 100644 index 00000000..c38f30dd --- /dev/null +++ b/data/30-vendor-microsoft.quirks @@ -0,0 +1,16 @@ +[Microsoft Surface 3 Lid Switch] +MatchName=*Lid Switch* +MatchDMIModalias=dmi:*svnMicrosoftCorporation:pnSurface3:* +AttrLidSwitchReliability=write_open + +[Microsoft Surface 3 Type Cover Keyboard] +MatchName=*Microsoft Surface Type Cover Keyboard* +MatchDMIModalias=dmi:*svnMicrosoftCorporation:pnSurface3:* +AttrKeyboardIntegration=internal + +[Microsoft Nano Transceiver v2.0] +MatchUdevType=mouse +MatchBus=usb +MatchVendor=0x045E +MatchProduct=0x8000 +ModelBouncingKeys=1 diff --git a/data/30-vendor-razer.quirks b/data/30-vendor-razer.quirks new file mode 100644 index 00000000..f69e1e54 --- /dev/null +++ b/data/30-vendor-razer.quirks @@ -0,0 +1,11 @@ +[Razer Blade Keyboard] +MatchUdevType=keyboard +MatchBus=usb +MatchVendor=0x1532 +MatchProduct=0x0220 +AttrKeyboardIntegration=internal + +[Razer Blade Lid Switch] +MatchName=*Lid Switch* +MatchDMIModalias=dmi:*svnRazer:pnBlade* +AttrLidSwitchReliability=write_open diff --git a/data/30-vendor-synaptics.quirks b/data/30-vendor-synaptics.quirks new file mode 100644 index 00000000..ffb7cd58 --- /dev/null +++ b/data/30-vendor-synaptics.quirks @@ -0,0 +1,6 @@ +[Synaptics Serial Touchpads] +MatchUdevType=touchpad +MatchBus=ps2 +MatchVendor=0x0002 +MatchProduct=0x0007 +ModelSynapticsSerialTouchpad=1 diff --git a/data/30-vendor-wacom.quirks b/data/30-vendor-wacom.quirks new file mode 100644 index 00000000..87a2eeca --- /dev/null +++ b/data/30-vendor-wacom.quirks @@ -0,0 +1,12 @@ +[Wacom Touchpads] +MatchUdevType=touchpad +MatchBus=usb +MatchVendor=0x056A +ModelWacomTouchpad=1 + +[Wacom Intuos Pro PTH660] +MatchUdevType=touchpad +MatchBus=usb +MatchVendor=0x056A +MatchProduct=0x0357 +AttrPalmSizeThreshold=1 diff --git a/data/50-system-apple.quirks b/data/50-system-apple.quirks new file mode 100644 index 00000000..0b1552a0 --- /dev/null +++ b/data/50-system-apple.quirks @@ -0,0 +1,48 @@ +[Apple Touchpads USB] +MatchVendor=0x05AC +MatchBus=usb +MatchUdevType=touchpad +ModelAppleTouchpad=1 +AttrSizeHint=104x75 +AttrTouchSizeRange=150:130 +AttrPalmSizeThreshold=800 + +[Apple Touchpads Bluetooth] +MatchVendor=0x05AC +MatchBus=bluetooth +MatchUdevType=touchpad +ModelAppleTouchpad=1 + +[Apple Internal Keyboard] +MatchName=*Apple Inc. Apple Internal Keyboard* +AttrKeyboardIntegration=internal + +[Apple MagicMouse] +MatchUdevType=mouse +MatchBus=bluetooth +MatchVendor=0x05AC +MatchProduct=0x030D +ModelAppleMagicMouse=1 + +[Apple Magic Trackpad v1 (2010, clickpad)] +MatchUdevType=touchpad +MatchBus=bluetooth +MatchVendor=0x5AC +MatchProduct=0x030E +AttrSizeHint=130x110 +AttrTouchSizeRange=20:10 +AttrPalmSizeThreshold=900 + +[Apple Touchpad OneButton] +MatchUdevType=touchpad +MatchBus=usb +MatchVendor=0x5AC +MatchProduct=0x021A +ModelAppleTouchpadOneButton=1 + +[Apple Touchpad MacbookPro5,5] +MatchUdevType=touchpad +MatchBus=usb +MatchVendor=0x05AC +MatchProduct=0x0237 +AttrPalmSizeThreshold=1000 diff --git a/data/50-system-asus.quirks b/data/50-system-asus.quirks new file mode 100644 index 00000000..ad120df5 --- /dev/null +++ b/data/50-system-asus.quirks @@ -0,0 +1,9 @@ +[Asus X555LAB] +MatchName=*ETPS/2 Elantech Touchpad* +MatchDMIModalias=dmi:*svnASUSTeKCOMPUTERINC.:pnX555LAB:* +ModelTouchpadVisibleMarker=1 + +[Asus UX21E] +MatchName=*ETPS/2 Elantech Touchpad* +MatchDMIModalias=dmi:*svnASUSTeKComputerInc.:pnUX21E:* +AttrPressureRange=24:10 diff --git a/data/50-system-chicony.quirks b/data/50-system-chicony.quirks new file mode 100644 index 00000000..808427f5 --- /dev/null +++ b/data/50-system-chicony.quirks @@ -0,0 +1,7 @@ +# Acer Hawaii Keyboard, uses Chicony VID +[Acer Hawaii Keyboard] +MatchUdevType=touchpad +MatchBus=usb +MatchVendor=0x4F2 +MatchProduct=0x1558 +AttrTPKComboLayout=below diff --git a/data/50-system-cyborg.quirks b/data/50-system-cyborg.quirks new file mode 100644 index 00000000..06137ae9 --- /dev/null +++ b/data/50-system-cyborg.quirks @@ -0,0 +1,6 @@ +[Saitek Cyborg RAT5] +MatchUdevType=mouse +MatchBus=usb +MatchVendor=0x06A3 +MatchProduct=0x0CD5 +ModelCyborgRat=1 diff --git a/data/50-system-dell.quirks b/data/50-system-dell.quirks new file mode 100644 index 00000000..3df31163 --- /dev/null +++ b/data/50-system-dell.quirks @@ -0,0 +1,15 @@ +[Dell Touchpads] +MatchName=* Touchpad +MatchDMIModalias=dmi:*svnDellInc.:* +ModelTouchpadVisibleMarker=1 + +[Dell Lattitude E6220] +MatchName=*AlpsPS/2 ALPS GlidePoint +MatchDMIModalias=dmi:*svnDellInc.:pnLatitudeE6220:* +AttrPressureRange=100:90 + +[Dell XPS L322X] +MatchName=*CyPS/2 Cypress Trackpad +MatchDMIModalias=dmi:*svnDell*:XPSL322X* +AttrPressureRange=32:20 +AttrPalmPressureThreshold=254 diff --git a/data/50-system-google.quirks b/data/50-system-google.quirks new file mode 100644 index 00000000..14f727d0 --- /dev/null +++ b/data/50-system-google.quirks @@ -0,0 +1,86 @@ +[Google Chromebook R13 CB5-312T] +MatchName=*Elan Touchpad* +MatchDeviceTree=*Chromebook R13 CB5-312T* +AttrPressureRange=6:4 + +[Google Chromebook CB5-312T] +MatchName=*Elan Touchpad* +MatchDeviceTree=*CB5-312T* +AttrPressureRange=6:4 + +[Google Chromebook Elm] +MatchName=*Elan Touchpad* +MatchDeviceTree=*Elm* +AttrPressureRange=6:4 + +[Google Chromebook Falco] +MatchUdevType=touchpad +MatchName=Cypress APA Trackpad ?cyapa? +MatchDMIModalias=dmi:*pn*Falco* +ModelChromebook=1 + +[Google Chromebook Mario] +MatchUdevType=touchpad +MatchName=SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*pn*Mario* +ModelChromebook=1 + +[Google Chromebook Butterfly] +MatchUdevType=touchpad +MatchName=Cypress APA Trackpad ?cyapa? +MatchDMIModalias=dmi:*pn*Butterfly* +ModelChromebook=1 + +[Google Chromebook Peppy] +MatchUdevType=touchpad +MatchName=Cypress APA Trackpad ?cyapa? +MatchDMIModalias=dmi:*pn*Peppy* +ModelChromebook=1 + +[Google Chromebook ZGB] +MatchUdevType=touchpad +MatchName=SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*pn*ZGB* +ModelChromebook=1 + +[Google Chromebook Parrot] +MatchUdevType=touchpad +MatchName=Cypress APA Trackpad ?cyapa? +MatchDMIModalias=dmi:*pn*Parrot* +ModelChromebook=1 + +[Google Chromebook Leon] +MatchUdevType=touchpad +MatchName=Cypress APA Trackpad ?cyapa? +MatchDMIModalias=dmi:*bvn*coreboot*:pn*Leon* +ModelChromebook=1 + +[Google Chromebook Wolf] +MatchUdevType=touchpad +MatchName=Cypress APA Trackpad ?cyapa? +MatchDMIModalias=dmi:*bvn*coreboot*:pn*Wolf* +ModelChromebook=1 + +[Google Chromebook Link] +MatchUdevType=touchpad +MatchName=Cypress APA Trackpad ?cyapa? +MatchDMIModalias=dmi:*svn*GOOGLE*:pn*Link* +ModelChromebook=1 + +[Google Chromebook Alex] +MatchUdevType=touchpad +MatchName=SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*pn*Alex* +ModelChromebook=1 + +[Google Chromebook Lumpy] +MatchUdevType=touchpad +MatchName=Cypress APA Trackpad ?cyapa? +MatchDMIModalias=dmi:*svn*SAMSUNG*:pn*Lumpy* +ModelChromebook=1 + +[Google Chromebook Samus] +MatchUdevType=touchpad +MatchName=Atmel maXTouch Touchpad +MatchDMIModalias=dmi:*svn*GOOGLE*:pn*Samus* +ModelChromebook=1 diff --git a/data/50-system-hp.quirks b/data/50-system-hp.quirks new file mode 100644 index 00000000..0d127dcd --- /dev/null +++ b/data/50-system-hp.quirks @@ -0,0 +1,24 @@ +[HP Compaq 6910p] +MatchName=*SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*svnHewlett-Packard:*pnHPCompaq6910p* +ModelHP6910Touchpad=1 + +[HP Compaq 8510w] +MatchName=*SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*svnHewlett-Packard:*pnHPCompaq8510w* +ModelHP8510Touchpad=1 + +[HP Pavillion dmi4] +MatchName=*SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*svnHewlett-Packard:*pnHPPaviliondm4NotebookPC* +ModelHPPavilionDM4Touchpad=1 + +[HP Stream 11] +MatchName=SYN1EDE:00 06CB:7442 +MatchDMIModalias=dmi:*svnHewlett-Packard:pnHPStreamNotebookPC11* +ModelHPStream11Touchpad=1 + +[HP ZBook Studio G3] +MatchName=AlpsPS/2 ALPS GlidePoint +MatchDMIModalias=dmi:*svnHP:pnHPZBookStudioG3:* +ModelHPZBookStudioG3=1 diff --git a/data/50-system-lenovo.quirks b/data/50-system-lenovo.quirks new file mode 100644 index 00000000..b365fc3c --- /dev/null +++ b/data/50-system-lenovo.quirks @@ -0,0 +1,78 @@ +[Lenovo Thinkpad Touchpad] +MatchName=*Synaptics* +MatchDMIModalias=dmi:*svnLENOVO:*:pvrThinkPad*:* +AttrThumbPressureThreshold=100 + +[Lenovo x230 Touchpad] +MatchName=*SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*svnLENOVO:*:pvrThinkPadX230* +ModelLenovoX230=1 + +[Lenovo T440p Touchpad PS/2] +MatchName=SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*svnLENOVO:*:pvrThinkPadT440p* +ModelLenovoT450Touchpad=1 + +[Lenovo T440p Touchpad RMI4] +MatchName=Synaptics tm2964-001 +MatchDMIModalias=dmi:*svnLENOVO:*:pvrThinkPadT440p* +ModelLenovoT450Touchpad=1 + +[Lenovo T440s Trackpoint] +MatchName=TPPS/2 IBM TrackPoint +MatchDMIModalias=dmi:*svnLENOVO:*:pvrThinkPadT440s* +AttrTrackpointRange=30 + +[Lenovo T440s Trackpoint] +MatchName=TPPS/2 IBM TrackPoint +MatchDMIModalias=dmi:*svnLENOVO:*:pvrThinkPadT450s* +AttrTrackpointRange=50 + +[Lenovo P50 Touchpad] +MatchName=SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*svnLENOVO:*:pvrThinkPadP50*: +ModelLenovoT450Touchpad=1 +AttrPalmPressureThreshold=150 + +[Lenovo *50 Touchpad] +MatchName=SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*svnLENOVO:*:pvrThinkPad??50*: +ModelLenovoT450Touchpad=1 +AttrPalmPressureThreshold=150 + +[Lenovo *60 Touchpad] +MatchName=SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*svnLENOVO:*:pvrThinkPad??60*: +ModelLenovoT450Touchpad=1 +AttrPalmPressureThreshold=150 + +[Lenovo X1 Carbon 3rd Touchpad] +MatchName=SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*svnLENOVO:*:pvrThinkPadX1Carbon3rd:* +ModelLenovoT450Touchpad=1 +AttrPalmPressureThreshold=150 + +[Lenovo ThinkPad Compact USB Keyboard with TrackPoint] +MatchUdevType=keyboard +MatchBus=usb +MatchVendor=0x17EF +MatchProduct=0x6047 +AttrKeyboardIntegration=external + +[Lenovo X280 Trackpoint] +MatchName=*ALPS TrackPoint* +MatchDMIModalias=dmi:*svnLENOVO:*:pvrThinkPadX280:* +AttrTrackpointRange=70 + +# Lenovo Thinkpad X1 Yoga disables the keyboard anyway but has the same device +# use a windows key on the screen and volume rocker on the side (#103749) +[Lenovo Thinkpad X1 Yoga] +MatchName=AT Translated Set 2 keyboard +MatchDMIModalias=dmi:*svnLENOVO:*pvrThinkPadX1Yoga1st:* +ModelTabletModeNoSuspend=1 + +# Lenovo Carbon X1 6th gen (RMI4 only, PS/2 is broken on this device) +[Lenovo Carbon X1 6th gen] +MatchName=Synaptics TM3288-010 +MatchDMIModalias=dmi:*svnLenovo:*pvrThinkPadX1Carbon6th:* +ModelLenovoCarbonX16th=1 diff --git a/data/50-system-system76.quirks b/data/50-system-system76.quirks new file mode 100644 index 00000000..15dd6108 --- /dev/null +++ b/data/50-system-system76.quirks @@ -0,0 +1,19 @@ +[System76 Bonobo Professional] +MatchName=SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*svnSystem76*pvrbonp5* +ModelSystem76Bonobo=1 + +[System76 Clevo] +MatchName=SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*pnW740SU*rnW740SU* +ModelClevoW740SU=1 + +[System76 Galago Ultra Pro] +MatchName=SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*svnSystem76*pvrgalu1* +ModelSystem76Galago=1 + +[System76 Kudu Professional] +MatchName=SynPS/2 Synaptics TouchPad +MatchDMIModalias=dmi:*svnSystem76*pvrkudp1* +ModelSystem76Kudu=1 diff --git a/data/README.md b/data/README.md new file mode 100644 index 00000000..66897938 --- /dev/null +++ b/data/README.md @@ -0,0 +1,78 @@ += libinput data file format = + +This directory contains hardware quirks used by libinput to work around bugs +in the hardware, device behavior and to supply information not obtained +through the kernel device. + +**THIS IS NOT STABLE API** + +The data format may change at any time. If your data file is not part of the +libinput git tree, do not expect it to work after an update. Absolutely no +guarantees are made for backwards-compatibility. + +**THIS IS NOT A CONFIGURATION API** + +Use the `libinput_device_config_foo()` functions for device configuration. +The quirks are hardware quirks only. + +== Data file naming == + +Data files are read in versionsort order, read order determines how values +override each other. A values read later override previously values. The +current structure is 10-generic-foo.quirks for generic settings, +30-vendor-foo.quirks for vendor-specific settings and 50-system-foo.quirks +for system vendors. This is not a fixed naming scheme and may change at any +time. It's an approximation only because some vendors are also system +vendors, e.g. Microsoft makes devices and laptops. + +Laptop-specific quirks should always go into the laptop vendor's file. + +== Sections, matches and values == + +A data file must contain at least one section, each section must have at +least one `Match` tag and at least one of either `Attr` or `Model`. Section +names are free-form and may contain spaces. + +``` +# This is a comment +[Some touchpad] +MatchBus=usb +# No quotes around strings +MatchName=*Synaptics Touchpad* +AttrSizeHint=50x50 +ModelSynapticsTouchpad=1 + +[Apple touchpad] +MatchVendor=0x5AC +MatchProduct=0x123 +ModelAppleTouchpad=1 +``` + +Comments are lines starting with `#`. + +All `Model` tags take a value of either `1` or `0`. + +All `Attr` tag values are specific to that attribute. + +== Parser errors == + +The following will cause parser errors and are considered invalid data +files: + +* Whitespace at the beginning of the line +* Inline comments, e.g. `MatchBus=usb # oops, fail` +* Sections without at least one `Match*` entry +* Sections with the same `Match*` entry repeated +* Sections without at least one of `Model*` or `Attr` entries +* A `Model` tag with a value other than `1` or `0` +* A string property with enclosing quotes + +== Debugging == + +When modifying a data file, use the `libinput list-quirks` tool to +verify the changes. The tool can be pointed at the data directory to +analyse, use `--verbose` to get more info. For example: + +``` +libinput list-quirks --data-dir /path/to/git/repo/data/ --verbose /dev/input/event0 +``` diff --git a/doc/device-quirks.dox b/doc/device-quirks.dox new file mode 100644 index 00000000..35e78215 --- /dev/null +++ b/doc/device-quirks.dox @@ -0,0 +1,102 @@ +/** +@page device-quirks Device quirks + +libinput requires extra information from devices that is not always readily +available. For example, some touchpads are known to have jumping cursors +under specific conditions. libinput ships a set of files containting the +so-called model quirks to provide that information. Model quirks are usually +installed under `/usr/share/libinput/<filename>.quirks` and are standard +`.ini` files. A file may contain multiple section headers (`[some +identifier]`) followed by one or more `MatchFoo=Bar` directives, followed by +at least one of `ModelFoo=1` or `AttrFoo=bar` directive. See the +`data/README.md` file in the libinput source repository for more details on +their contents. + +@note Model quirks are internal API and may change at any time. No +backwards-compatibility is guaranteed. + +For example, a quirks file may have this content to label all keyboards on +the serial bus (PS/2) as internal keyboards: + +@verbatim +[Serial Keyboards] +MatchUdevType=keyboard +MatchBus=serial +AttrKeyboardIntegration=internal +@endverbatim + +The model quirks are part of the source distribution and should never be +modified locally. Updates to libinput may overwrite modifications or even +stop parsing any property. For temporary local workarounds, see @ref +device-quirks-local. + +Device quirks are parsed on libinput initialization. A parsing error in the +device quirks disables **all** device quirks and may negatively impact +device behavior on the host. If the quirks cannot be loaded, an error +message is posted to the log and users should use the information in @ref +device-quirks-debugging to verify their quirks files. + +@section device-quirks-local Installing temporary local device quirks + +The model quirks are part of the source distribution and should never be +modified. For temporary local workarounds, libinput reads the +`/etc/libinput/local-overrides.quirks` file. Users may add a sections to +this file to add a device quirk for a local device but beware that **any +modification must be upstreamed** or it may cease to work at any time. + +@note Model quirks are internal API and may change at any time. No +backwards-compatibility is guaranteed. Local overrides should only be used +until the distribution updates the libinput packages. + +The `local-overrides.quirks` file usually needs to be created by the user. +Once the required section has been added, use the information from section +@ref device-quirks-debugging to validate and test the quirks. + +@section device-quirks-debugging Debugging device quirks + +libinput provides the `libinput list-quirks` tool to list and debug model +quirks that apply to one or more local devices. + +@verbatim +$ libinput list-quirks /dev/input/event19 +Device has no quirks defined +$ libinput list-quirks /dev/input/event0 +AttrLidSwitchReliability +@endverbatim + +When called with the `--verbose` argument, `libinput list-quirks` prints +information about all files and its attempts to match the device: + +@verbatim +$ libinput list-quirks --verbose /dev/input/event0 +quirks debug: /usr/share/share/libinput is data root +quirks debug: /usr/share/share/libinput/10-generic-keyboard.quirks +quirks debug: /usr/share/share/libinput/10-generic-lid.quirks +[...] +quirks debug: /usr/share/etc/libinput/local-overrides.quirks +quirks debug: /dev/input/event0: fetching quirks +quirks debug: [Serial Keyboards] (10-generic-keyboard.quirks) wants MatchBus but we don't have that +quirks debug: [Lid Switch Ct9] (10-generic-lid.quirks) matches for MatchName +quirks debug: [Lid Switch Ct10] (10-generic-lid.quirks) matches for MatchName +quirks debug: [Lid Switch Ct10] (10-generic-lid.quirks) matches for MatchDMIModalias +quirks debug: [Lid Switch Ct10] (10-generic-lid.quirks) is full match +quirks debug: property added: AttrLidSwitchReliability from [Lid Switch Ct10] (10-generic-lid.quirks) +quirks debug: [Aiptek No Tilt Tablet] (30-vendor-aiptek.quirks) wants MatchBus but we don't have that +[...] +quirks debug: [HUION PenTablet] (30-vendor-huion.quirks) wants MatchBus but we don't have that +quirks debug: [Logitech Marble Mouse Trackball] (30-vendor-logitech.quirks) wants MatchBus but we don't have that +quirks debug: [Logitech K400] (30-vendor-logitech.quirks) wants MatchBus but we don't have that +quirks debug: [Logitech K400r] (30-vendor-logitech.quirks) wants MatchBus but we don't have that +quirks debug: [Logitech K830] (30-vendor-logitech.quirks) wants MatchBus but we don't have that +quirks debug: [Logitech K400Plus] (30-vendor-logitech.quirks) wants MatchBus but we don't have that +quirks debug: [Logitech Wireless Touchpad] (30-vendor-logitech.quirks) wants MatchBus but we don't have that +quirks debug: [Microsoft Surface 3 Lid Switch] (30-vendor-microsoft.quirks) matches for MatchName +[...] +AttrLidSwitchReliability +@endverbatim + +Note that this is an example only, the output may change over time. The tool +uses the same parser as libinput and any parsing errors will show up in the +output. + +*/ diff --git a/doc/page-hierarchy.dox b/doc/page-hierarchy.dox index 6e164f53..834b2f4b 100644 --- a/doc/page-hierarchy.dox +++ b/doc/page-hierarchy.dox @@ -37,6 +37,7 @@ @page general General +- @subpage device-quirks - @subpage udev_config - @subpage seats - @subpage timestamps diff --git a/meson.build b/meson.build index 69a386c7..b8735ced 100644 --- a/meson.build +++ b/meson.build @@ -181,6 +181,54 @@ libfilter = static_library('filter', src_libfilter, include_directories : includes_include) dep_libfilter = declare_dependency(link_with : libfilter) +############ libquirks.a ############# +libinput_data_path = join_paths(get_option('prefix'), get_option('datadir'), 'libinput') +libinput_data_override_path = join_paths(get_option('prefix'), + get_option('sysconfdir'), + 'libinput', + 'local-overrides.quirks') +config_h.set_quoted('LIBINPUT_DATA_DIR', libinput_data_path) +config_h.set_quoted('LIBINPUT_DATA_OVERRIDE_FILE', libinput_data_override_path) + +quirks_data = [ + 'data/10-generic-keyboard.quirks', + 'data/10-generic-lid.quirks', + 'data/10-generic-trackball.quirks', + 'data/30-vendor-aiptek.quirks', + 'data/30-vendor-alps.quirks', + 'data/30-vendor-cyapa.quirks', + 'data/30-vendor-elantech.quirks', + 'data/30-vendor-huion.quirks', + 'data/30-vendor-ibm.quirks', + 'data/30-vendor-logitech.quirks', + 'data/30-vendor-microsoft.quirks', + 'data/30-vendor-razer.quirks', + 'data/30-vendor-synaptics.quirks', + 'data/30-vendor-wacom.quirks', + 'data/50-system-apple.quirks', + 'data/50-system-asus.quirks', + 'data/50-system-chicony.quirks', + 'data/50-system-cyborg.quirks', + 'data/50-system-dell.quirks', + 'data/50-system-google.quirks', + 'data/50-system-hp.quirks', + 'data/50-system-lenovo.quirks', + 'data/50-system-system76.quirks', +] + +install_data(quirks_data, install_dir : libinput_data_path) + +src_libquirks = [ + 'src/quirks.c', + 'src/quirks.h', +] + +deps_libquirks = [dep_udev, dep_libinput_util] +libquirks = static_library('quirks', src_libquirks, + dependencies : deps_libquirks, + include_directories : includes_include) +dep_libquirks = declare_dependency(link_with : libquirks) + ############ libinput.so ############ install_headers('src/libinput.h') src_libinput = src_libfilter + [ @@ -220,7 +268,8 @@ deps_libinput = [ dep_lm, dep_rt, dep_libwacom, - dep_libinput_util + dep_libinput_util, + dep_libquirks ] libinput_version_h_config = configuration_data() @@ -317,6 +366,7 @@ if get_option('documentation') meson.source_root() + '/doc/clickpad-softbuttons.dox', meson.source_root() + '/doc/contributing.dox', meson.source_root() + '/doc/device-configuration-via-udev.dox', + meson.source_root() + '/doc/device-quirks.dox', meson.source_root() + '/doc/faqs.dox', meson.source_root() + '/doc/gestures.dox', meson.source_root() + '/doc/middle-button-emulation.dox', @@ -435,6 +485,22 @@ configure_file(input : 'tools/libinput-debug-events.man', install_dir : join_paths(get_option('mandir'), 'man1') ) +libinput_list_quirks_sources = [ 'tools/libinput-list-quirks.c' ] +executable('libinput-list-quirks', + libinput_list_quirks_sources, + dependencies : [dep_libquirks, dep_libinput], + include_directories : [includes_src, includes_include], + install_dir : libinput_tool_path, + install : true + ) + +configure_file(input : 'tools/libinput-list-quirks.man', + output : 'libinput-list-quirks.1', + configuration : man_config, + install : true, + install_dir : join_paths(get_option('mandir'), 'man1') + ) + libinput_list_devices_sources = [ 'tools/libinput-list-devices.c' ] executable('libinput-list-devices', libinput_list_devices_sources, @@ -697,6 +763,7 @@ if get_option('tests') dep_dl, dep_lm, dep_libsystemd, + dep_libquirks, ] configure_file(input : 'udev/80-libinput-test-device.rules', @@ -763,7 +830,8 @@ if get_option('tests') 'test/test-keyboard.c', 'test/test-device.c', 'test/test-gestures.c', - 'test/test-switch.c' + 'test/test-switch.c', + 'test/test-quirks.c', ] def_LT_VERSION = '-DLIBINPUT_LT_VERSION="@0@:@1@:@2@"'.format(libinput_lt_c, libinput_lt_r, libinput_lt_a) libinput_test_runner = executable('libinput-test-suite-runner', diff --git a/src/quirks.c b/src/quirks.c new file mode 100644 index 00000000..7cef8a79 --- /dev/null +++ b/src/quirks.c @@ -0,0 +1,1437 @@ +/* + * Copyright © 2018 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +/* This has the hallmarks of a library to make it re-usable from the tests + * and from the list-quirks tool. It doesn't have all of the features from a + * library you'd expect though + */ + +#undef NDEBUG /* You don't get to disable asserts here */ +#include <assert.h> +#include <stdlib.h> +#include <libudev.h> +#include <dirent.h> +#include <fnmatch.h> + +#include "libinput-util.h" +#include "libinput-private.h" + +#include "quirks.h" + +/* Custom logging so we can have detailed output for the tool but minimal + * logging for libinput itself. */ +#define qlog_debug(ctx_, ...) quirk_log_msg((ctx_), QLOG_NOISE, __VA_ARGS__) +#define qlog_info(ctx_, ...) quirk_log_msg((ctx_), QLOG_INFO, __VA_ARGS__) +#define qlog_error(ctx_, ...) quirk_log_msg((ctx_), QLOG_ERROR, __VA_ARGS__) +#define qlog_parser(ctx_, ...) quirk_log_msg((ctx_), QLOG_PARSER_ERROR, __VA_ARGS__) + +enum property_type { + PT_UINT, + PT_INT, + PT_STRING, + PT_BOOL, + PT_DIMENSION, + PT_RANGE, +}; + +/** + * Generic value holder for the property types we support. The type + * identifies which value in the union is defined and we expect callers to + * already know which type yields which value. + */ +struct property { + size_t refcount; + struct list link; /* struct sections.properties */ + + enum quirk id; + enum property_type type; + union { + bool b; + uint32_t u; + int32_t i; + char *s; + struct quirk_dimensions dim; + struct quirk_range range; + } value; +}; + +enum match_flags { + M_NAME = (1 << 0), + M_BUS = (1 << 1), + M_VID = (1 << 2), + M_PID = (1 << 3), + M_DMI = (1 << 4), + M_UDEV_TYPE = (1 << 5), + M_DT = (1 << 6), + + M_LAST = M_DT, +}; + +enum bustype { + BT_UNKNOWN, + BT_USB, + BT_BLUETOOTH, + BT_PS2, + BT_RMI, + BT_I2C, +}; + +enum udev_type { + UDEV_MOUSE = (1 << 1), + UDEV_POINTINGSTICK = (1 << 2), + UDEV_TOUCHPAD = (1 << 3), + UDEV_TABLET = (1 << 4), + UDEV_TABLET_PAD = (1 << 5), + UDEV_JOYSTICK = (1 << 6), + UDEV_KEYBOARD = (1 << 7), +}; + +/** + * Contains the combined set of matches for one section or the values for + * one device. + * + * bits defines which fields are set, the rest is zero. + */ +struct match { + uint32_t bits; + + char *name; + enum bustype bus; + uint32_t vendor; + uint32_t product; + + char *dmi; + + /* We can have more than one type set, so this is a bitfield */ + uint32_t udev_type; + + char *dt; /* FIXME: clarify */ +}; + +/** + * Represents one section in the .quirks file. + */ +struct section { + struct list link; + + bool has_match; /* to check for empty sections */ + bool has_property; /* to check for empty sections */ + + char *name; /* the [Section Name] */ + struct match match; + struct list properties; +}; + +/** + * The struct returned to the caller. It contains the + * properties for a given device. + */ +struct quirks { + size_t refcount; + struct list link; /* struct quirks_context.quirks */ + + /* These are not ref'd, just a collection of pointers */ + struct property **properties; + size_t nproperties; +}; + +/** + * Quirk matching context, initialized once with quirks_init_subsystem() + */ +struct quirks_context { + size_t refcount; + + libinput_log_handler log_handler; + enum quirks_log_type log_type; + struct libinput *libinput; /* for logging */ + + char *dmi; + + struct list sections; + + /* list of quirks handed to libinput, just for bookkeeping */ + struct list quirks; +}; + +LIBINPUT_ATTRIBUTE_PRINTF(3, 0) +static inline void +quirk_log_msg_va(struct quirks_context *ctx, + enum quirks_log_priorities priority, + const char *format, + va_list args) +{ + enum libinput_log_priority p = priority; + + switch (priority) { + /* We don't use this if we're logging through libinput */ + default: + case QLOG_NOISE: + case QLOG_PARSER_ERROR: + if (ctx->log_type == QLOG_LIBINPUT_LOGGING) + return; + break; + case QLOG_DEBUG: /* These map straight to libinput priorities */ + case QLOG_INFO: + case QLOG_ERROR: + break; + } + + ctx->log_handler(ctx->libinput, p, format, args); +} + +LIBINPUT_ATTRIBUTE_PRINTF(3, 4) +static inline void +quirk_log_msg(struct quirks_context *ctx, + enum quirks_log_priorities priority, + const char *format, + ...) +{ + va_list args; + + va_start(args, format); + quirk_log_msg_va(ctx, priority, format, args); + va_end(args); + +} + +const char * +quirk_get_name(enum quirk q) +{ + switch(q) { + case QUIRK_MODEL_ALPS_TOUCHPAD: return "ModelALPSTouchpad"; + case QUIRK_MODEL_APPLE_TOUCHPAD: return "ModelAppleTouchpad"; + case QUIRK_MODEL_APPLE_MAGICMOUSE: return "ModelAppleMagicMouse"; + case QUIRK_MODEL_TABLET_NO_TILT: return "ModelTabletNoTilt"; + case QUIRK_MODEL_APPLE_TOUCHPAD_ONEBUTTON: return "ModelAppleTouchpadOneButton"; + case QUIRK_MODEL_TOUCHPAD_VISIBLE_MARKER: return "ModelTouchpadVisibleMarker"; + case QUIRK_MODEL_CYBORG_RAT: return "ModelCyborgRat"; + case QUIRK_MODEL_CHROMEBOOK: return "ModelChromebook"; + case QUIRK_MODEL_HP6910_TOUCHPAD: return "ModelHP6910Touchpad"; + case QUIRK_MODEL_HP8510_TOUCHPAD: return "ModelHP8510Touchpad"; + case QUIRK_MODEL_HP_PAVILION_DM4_TOUCHPAD: return "ModelHPPavilionDM4Touchpad"; + case QUIRK_MODEL_HP_STREAM11_TOUCHPAD: return "ModelHPStream11Touchpad"; + case QUIRK_MODEL_HP_ZBOOK_STUDIO_G3: return "ModelHPZBookStudioG3"; + case QUIRK_MODEL_TABLET_NO_PROXIMITY_OUT: return "ModelTabletNoProximityOut"; + case QUIRK_MODEL_LENOVO_SCROLLPOINT: return "ModelLenovoScrollPoint"; + case QUIRK_MODEL_LENOVO_X230: return "ModelLenovoX230"; + case QUIRK_MODEL_LENOVO_T450_TOUCHPAD: return "ModelLenovoT450Touchpad"; + case QUIRK_MODEL_TABLET_MODE_NO_SUSPEND: return "ModelTabletModeNoSuspend"; + case QUIRK_MODEL_LENOVO_CARBON_X1_6TH: return "ModelLenovoCarbonX16th"; + case QUIRK_MODEL_TRACKBALL: return "ModelTrackball"; + case QUIRK_MODEL_LOGITECH_MARBLE_MOUSE: return "ModelLogitechMarbleMouse"; + case QUIRK_MODEL_BOUNCING_KEYS: return "ModelBouncingKeys"; + case QUIRK_MODEL_SYNAPTICS_SERIAL_TOUCHPAD: return "ModelSynapticsSerialTouchpad"; + case QUIRK_MODEL_SYSTEM76_BONOBO: return "ModelSystem76Bonobo"; + case QUIRK_MODEL_CLEVO_W740SU: return "ModelClevoW740SU"; + case QUIRK_MODEL_SYSTEM76_GALAGO: return "ModelSystem76Galago"; + case QUIRK_MODEL_SYSTEM76_KUDU: return "ModelSystem76Kudu"; + case QUIRK_MODEL_WACOM_TOUCHPAD: return "ModelWacomTouchpad"; + case QUIRK_MODEL_JUMPING_SEMI_MT: return "ModelJumpingSemiMT"; + + case QUIRK_ATTR_SIZE_HINT: return "AttrSizeHint"; + case QUIRK_ATTR_TOUCH_SIZE_RANGE: return "AttrTouchSizeRange"; + case QUIRK_ATTR_PALM_SIZE_THRESHOLD: return "AttrPalmSizeThreshold"; + case QUIRK_ATTR_LID_SWITCH_RELIABILITY: return "AttrLidSwitchReliability"; + case QUIRK_ATTR_KEYBOARD_INTEGRATION: return "AttrKeyboardIntegration"; + case QUIRK_ATTR_TPKBCOMBO_LAYOUT: return "AttrTPKComboLayout"; + case QUIRK_ATTR_PRESSURE_RANGE: return "AttrPressureRange"; + case QUIRK_ATTR_PALM_PRESSURE_THRESHOLD: return "AttrPalmPressureThreshold"; + case QUIRK_ATTR_RESOLUTION_HINT: return "AttrResolutionHint"; + case QUIRK_ATTR_TRACKPOINT_RANGE: return "AttrTrackpointRange"; + case QUIRK_ATTR_THUMB_PRESSURE_THRESHOLD: return "AttrThumbPressureThreshold"; + default: + abort(); + } +} + +static inline const char * +matchflagname(enum match_flags f) +{ + switch(f) { + case M_NAME: return "MatchName"; break; + case M_BUS: return "MatchBus"; break; + case M_VID: return "MatchVendor"; break; + case M_PID: return "MatchProduct"; break; + case M_DMI: return "MatchDMIModalias"; break; + case M_UDEV_TYPE: return "MatchUdevType"; break; + case M_DT: return "MatchDeviceTree"; break; + default: + abort(); + } +}; + +static inline struct property * +property_new(void) +{ + struct property *p; + + p = zalloc(sizeof *p); + p->refcount = 1; + list_init(&p->link); + + return p; +} + +static inline struct property * +property_ref(struct property *p) +{ + assert(p->refcount > 0); + p->refcount++; + return p; +}; + +static inline struct property * +property_unref(struct property *p) +{ + /* Note: we don't cleanup here, that is a separate call so we + can abort if we haven't cleaned up correctly. */ + assert(p->refcount > 0); + p->refcount--; + + return NULL; +}; + +/* Separate call so we can verify that the caller unrefs the property + * before shutting down the subsystem. + */ +static inline void +property_cleanup(struct property *p) +{ + /* If we get here, the quirks must've been removed already */ + property_unref(p); + assert(p->refcount == 0); + + list_remove(&p->link); + if (p->type == PT_STRING) + free(p->value.s); + free(p); +} + +/** + * Return the dmi modalias from the udev device. + */ +static inline char * +init_dmi(void) +{ + struct udev *udev; + struct udev_device *udev_device; + const char *modalias; + char *copy = NULL; + const char *syspath = "/sys/devices/virtual/dmi/id"; + + udev = udev_new(); + if (!udev) + return NULL; + + udev_device = udev_device_new_from_syspath(udev, syspath); + if (udev_device) + modalias = udev_device_get_property_value(udev_device, + "MODALIAS"); + + /* Not sure whether this could ever really fail, if so we should + * open the sysfs file directly. But then udev wouldn't have failed, + * so... */ + if (!modalias) + modalias = "dmi:*"; + + copy = safe_strdup(modalias); + + udev_device_unref(udev_device); + udev_unref(udev); + + return copy; +} + +static inline struct section * +section_new(const char *path, const char *name) +{ + struct section *s = zalloc(sizeof(*s)); + + xasprintf(&s->name, "%s (%s)", name, basename(path)); + list_init(&s->link); + list_init(&s->properties); + + return s; +} + +static inline void +section_destroy(struct section *s) +{ + struct property *p, *tmp; + + free(s->name); + free(s->match.name); + free(s->match.dmi); + free(s->match.dt); + + list_for_each_safe(p, tmp, &s->properties, link) + property_cleanup(p); + + assert(list_empty(&s->properties)); + + list_remove(&s->link); + free(s); +} + +/** + * Parse a MatchFooBar=banana line. + * + * @param section The section struct to be filled in + * @param key The MatchFooBar part of the line + * @param value The banana part of the line. + * + * @return true on success, false otherwise. + */ +static bool +parse_match(struct quirks_context *ctx, + struct section *s, + const char *key, + const char *value) +{ + int rc = false; + +#define check_set_bit(s_, bit_) { \ + if ((s_)->match.bits & (bit_)) goto out; \ + (s_)->match.bits |= (bit_); \ + } + + assert(strlen(value) >= 1); + + if (streq(key, "MatchName")) { + check_set_bit(s, M_NAME); + s->match.name = safe_strdup(value); + } else if (streq(key, "MatchBus")) { + check_set_bit(s, M_BUS); + if (streq(value, "usb")) + s->match.bus = BT_USB; + else if (streq(value, "bluetooth")) + s->match.bus = BT_BLUETOOTH; + else if (streq(value, "ps2")) + s->match.bus = BT_PS2; + else if (streq(value, "rmi")) + s->match.bus = BT_RMI; + else if (streq(value, "i2c")) + s->match.bus = BT_I2C; + else + goto out; + } else if (streq(key, "MatchVendor")) { + unsigned int vendor; + + check_set_bit(s, M_VID); + if (!strneq(value, "0x", 2) || + !safe_atou_base(value, &vendor, 16) || + vendor > 0xFFFF) + goto out; + + s->match.vendor = vendor; + } else if (streq(key, "MatchProduct")) { + unsigned int product; + + check_set_bit(s, M_PID); + if (!strneq(value, "0x", 2) || + !safe_atou_base(value, &product, 16) || + product > 0xFFFF) + goto out; + + s->match.product = product; + } else if (streq(key, "MatchDMIModalias")) { + check_set_bit(s, M_DMI); + if (!strneq(value, "dmi:", 4)) { + qlog_parser(ctx, + "%s: MatchDMIModalias must start with 'dmi:'\n", + s->name); + goto out; + } + s->match.dmi = safe_strdup(value); + } else if (streq(key, "MatchUdevType")) { + check_set_bit(s, M_UDEV_TYPE); + if (streq(value, "touchpad")) + s->match.udev_type = UDEV_TOUCHPAD; + else if (streq(value, "mouse")) + s->match.udev_type = UDEV_MOUSE; + else if (streq(value, "pointingstick")) + s->match.udev_type = UDEV_POINTINGSTICK; + else if (streq(value, "keyboard")) + s->match.udev_type = UDEV_KEYBOARD; + else if (streq(value, "joystick")) + s->match.udev_type = UDEV_JOYSTICK; + else if (streq(value, "tablet")) + s->match.udev_type = UDEV_TABLET; + else if (streq(value, "tablet-pad")) + s->match.udev_type = UDEV_TABLET_PAD; + else + goto out; + } else if (streq(key, "MatchDeviceTree")) { + /* FIXME */ + } else { + qlog_error(ctx, "Unknown match key '%s'\n", key); + goto out; + } + +#undef check_set_bit + s->has_match = true; + rc = true; +out: + return rc; +} + +/** + * Parse a ModelFooBar=1 line. + * + * @param section The section struct to be filled in + * @param key The ModelFooBar part of the line + * @param value The value after the =, must be 1 or 0. + * + * @return true on success, false otherwise. + */ +static bool +parse_model(struct quirks_context *ctx, + struct section *s, + const char *key, + const char *value) +{ + enum quirk quirks[] = { + QUIRK_MODEL_ALPS_TOUCHPAD, + QUIRK_MODEL_APPLE_TOUCHPAD, + QUIRK_MODEL_APPLE_MAGICMOUSE, + QUIRK_MODEL_TABLET_NO_TILT, + QUIRK_MODEL_APPLE_TOUCHPAD_ONEBUTTON, + QUIRK_MODEL_TOUCHPAD_VISIBLE_MARKER, + QUIRK_MODEL_CYBORG_RAT, + QUIRK_MODEL_CHROMEBOOK, + QUIRK_MODEL_HP6910_TOUCHPAD, + QUIRK_MODEL_HP8510_TOUCHPAD, + QUIRK_MODEL_HP_PAVILION_DM4_TOUCHPAD, + QUIRK_MODEL_HP_STREAM11_TOUCHPAD, + QUIRK_MODEL_HP_ZBOOK_STUDIO_G3, + QUIRK_MODEL_TABLET_NO_PROXIMITY_OUT, + QUIRK_MODEL_LENOVO_SCROLLPOINT, + QUIRK_MODEL_LENOVO_X230, + QUIRK_MODEL_LENOVO_T450_TOUCHPAD, + QUIRK_MODEL_TABLET_MODE_NO_SUSPEND, + QUIRK_MODEL_LENOVO_CARBON_X1_6TH, + QUIRK_MODEL_TRACKBALL, + QUIRK_MODEL_LOGITECH_MARBLE_MOUSE, + QUIRK_MODEL_BOUNCING_KEYS, + QUIRK_MODEL_SYNAPTICS_SERIAL_TOUCHPAD, + QUIRK_MODEL_SYSTEM76_BONOBO, + QUIRK_MODEL_CLEVO_W740SU, + QUIRK_MODEL_SYSTEM76_GALAGO, + QUIRK_MODEL_SYSTEM76_KUDU, + QUIRK_MODEL_WACOM_TOUCHPAD, + QUIRK_MODEL_JUMPING_SEMI_MT, + }; + bool b; + enum quirk *q; + + assert(strneq(key, "Model", 5)); + + if (streq(value, "1")) + b = true; + else if (streq(value, "0")) + b = false; + else + return false; + + ARRAY_FOR_EACH(quirks, q) { + if (streq(key, quirk_get_name(*q))) { + struct property *p = property_new(); + p->id = *q, + p->type = PT_BOOL; + p->value.b = b; + list_append(&s->properties, &p->link); + s->has_property = true; + return true; + } + } + + qlog_error(ctx, "Unknown key %s in %s\n", key, s->name); + + return false; +} + +/** + * Parse a AttrFooBar=banana line. + * + * @param section The section struct to be filled in + * @param key The AttrFooBar part of the line + * @param value The banana part of the line. + * + * Value parsing depends on the attribute type. + * + * @return true on success, false otherwise. + */ +static inline bool +parse_attr(struct quirks_context *ctx, + struct section *s, + const char *key, + const char *value) +{ + struct property *p = property_new(); + bool rc = false; + struct quirk_dimensions dim; + struct quirk_range range; + unsigned int v; + + if (streq(key, quirk_get_name(QUIRK_ATTR_SIZE_HINT))) { + p->id = QUIRK_ATTR_SIZE_HINT; + if (!parse_dimension_property(value, &dim.x, &dim.y)) + goto out; + p->type = PT_DIMENSION; + p->value.dim = dim; + rc = true; + } else if (streq(key, quirk_get_name(QUIRK_ATTR_TOUCH_SIZE_RANGE))) { + p->id = QUIRK_ATTR_TOUCH_SIZE_RANGE; + if (!parse_range_property(value, &range.upper, &range.lower)) + goto out; + p->type = PT_RANGE; + p->value.range = range; + rc = true; + } else if (streq(key, quirk_get_name(QUIRK_ATTR_PALM_SIZE_THRESHOLD))) { + p->id = QUIRK_ATTR_PALM_SIZE_THRESHOLD; + if (!safe_atou(value, &v)) + goto out; + p->type = PT_UINT; + p->value.u = v; + rc = true; + } else if (streq(key, quirk_get_name(QUIRK_ATTR_LID_SWITCH_RELIABILITY))) { + p->id = QUIRK_ATTR_LID_SWITCH_RELIABILITY; + if (!streq(value, "reliable") && + !streq(value, "write_open")) + goto out; + p->type = PT_STRING; + p->value.s = safe_strdup(value); + rc = true; + } else if (streq(key, quirk_get_name(QUIRK_ATTR_KEYBOARD_INTEGRATION))) { + p->id = QUIRK_ATTR_KEYBOARD_INTEGRATION; + if (!streq(value, "internal") && !streq(value, "external")) + goto out; + p->type = PT_STRING; + p->value.s = safe_strdup(value); + rc = true; + } else if (streq(key, quirk_get_name(QUIRK_ATTR_TPKBCOMBO_LAYOUT))) { + p->id = QUIRK_ATTR_TPKBCOMBO_LAYOUT; + if (!streq(value, "below")) + goto out; + p->type = PT_STRING; + p->value.s = safe_strdup(value); + rc = true; + } else if (streq(key, quirk_get_name(QUIRK_ATTR_PRESSURE_RANGE))) { + p->id = QUIRK_ATTR_PRESSURE_RANGE; + if (!parse_range_property(value, &range.upper, &range.lower)) + goto out; + p->type = PT_RANGE; + p->value.range = range; + rc = true; + } else if (streq(key, quirk_get_name(QUIRK_ATTR_PALM_PRESSURE_THRESHOLD))) { + p->id = QUIRK_ATTR_PALM_PRESSURE_THRESHOLD; + if (!safe_atou(value, &v)) + goto out; + p->type = PT_UINT; + p->value.u = v; + rc = true; + } else if (streq(key, quirk_get_name(QUIRK_ATTR_RESOLUTION_HINT))) { + p->id = QUIRK_ATTR_RESOLUTION_HINT; + if (!parse_dimension_property(value, &dim.x, &dim.y)) + goto out; + p->type = PT_DIMENSION; + p->value.dim = dim; + rc = true; + } else if (streq(key, quirk_get_name(QUIRK_ATTR_TRACKPOINT_RANGE))) { + p->id = QUIRK_ATTR_TRACKPOINT_RANGE; + if (!safe_atou(value, &v)) + goto out; + p->type = PT_UINT; + p->value.u = v; + rc = true; + } else if (streq(key, quirk_get_name(QUIRK_ATTR_THUMB_PRESSURE_THRESHOLD))) { + p->id = QUIRK_ATTR_THUMB_PRESSURE_THRESHOLD; + if (!safe_atou(value, &v)) + goto out; + p->type = PT_UINT; + p->value.u = v; + rc = true; + } else { + qlog_error(ctx, "Unknown key %s in %s\n", key, s->name); + } +out: + if (rc) { + list_append(&s->properties, &p->link); + s->has_property = true; + } else { + property_cleanup(p); + } + return rc; +} + +/** + * Parse a single line, expected to be in the format Key=value. Anything + * else will be rejected with a failure. + * + * Our data files can only have Match, Model and Attr, so let's check for + * those too. + */ +static bool +parse_value_line(struct quirks_context *ctx, struct section *s, const char *line) +{ + char **strv; + const char *key, *value; + bool rc = false; + + strv = strv_from_string(line, "="); + if (strv[0] == NULL || strv[1] == NULL || strv[2] != NULL) { + goto out; + } + + + key = strv[0]; + value = strv[1]; + if (strlen(key) == 0 || strlen(value) == 0) + goto out; + + /* Whatever the value is, it's not supposed to be in quotes */ + if (value[0] == '"') + goto out; + + if (strneq(key, "Match", 5)) + rc = parse_match(ctx, s, key, value); + else if (strneq(key, "Model", 5)) + rc = parse_model(ctx, s, key, value); + else if (strneq(key, "Attr", 4)) + rc = parse_attr(ctx, s, key, value); + else + qlog_error(ctx, "Unknown value prefix %s\n", line); +out: + strv_free(strv); + return rc; +} + +static inline bool +parse_file(struct quirks_context *ctx, const char *path) +{ + enum state { + STATE_SECTION, + STATE_MATCH, + STATE_MATCH_OR_VALUE, + STATE_VALUE_OR_SECTION, + STATE_ANY, + }; + FILE *fp; + char line[512]; + bool rc = false; + enum state state = STATE_SECTION; + struct section *section = NULL; + int lineno = -1; + + qlog_debug(ctx, "%s\n", path); + + /* Not using open_restricted here, if we can't access + * our own data files, our installation is screwed up. + */ + fp = fopen(path, "r"); + if (!fp) { + /* If the file doesn't exist that's fine. Only way this can + * happen is for the custom override file, all others are + * provided by scandir so they do exist. Short of races we + * don't care about. */ + if (errno == ENOENT) + return true; + + qlog_error(ctx, "%s: failed to open file\n", path); + goto out; + } + + while (fgets(line, sizeof(line), fp)) { + lineno++; + if (strlen(line) >= 1 && line[strlen(line) - 1] == '\n') + line[strlen(line) - 1] = '\0'; /* drop trailing \n */ + + if (strlen(line) == 0) + continue; + + /* We don't use quotes for strings, so we really don't want + * erroneous trailing whitespaces */ + switch (line[strlen(line) - 1]) { + case ' ': + case '\t': + qlog_parser(ctx, + "%s:%d: Trailing whitespace '%s'\n", + path, lineno, line); + goto out; + } + + switch (line[0]) { + case '\0': + case '\n': + case '#': + break; + /* white space not allowed */ + case ' ': + case '\t': + qlog_parser(ctx, "%s:%d: Preceding whitespace '%s'\n", + path, lineno, line); + goto out; + /* section title */ + case '[': + if (line[strlen(line) - 1] != ']') { + qlog_parser(ctx, "%s:%d: Closing ] missing '%s'\n", + path, lineno, line); + goto out; + } + + if (state != STATE_SECTION && + state != STATE_VALUE_OR_SECTION) { + qlog_parser(ctx, "%s:%d: expected section before %s\n", + path, lineno, line); + goto out; + } + if (section && + (!section->has_match || !section->has_property)) { + qlog_parser(ctx, "%s:%d: previous section %s was empty\n", + path, lineno, section->name); + goto out; /* Previous section was empty */ + } + + state = STATE_MATCH; + section = section_new(path, line); + list_append(&ctx->sections, §ion->link); + break; + /* entries must start with A-Z */ + case 'A'...'Z': + switch (state) { + case STATE_SECTION: + qlog_parser(ctx, "%s:%d: expected [Section], got %s\n", + path, lineno, line); + goto out; + case STATE_MATCH: + if (!strneq(line, "Match", 5)) { + qlog_parser(ctx, "%s:%d: expected MatchFoo=bar, have %s\n", + path, lineno, line); + goto out; + } + state = STATE_MATCH_OR_VALUE; + break; + case STATE_MATCH_OR_VALUE: + if (!strneq(line, "Match", 5)) + state = STATE_VALUE_OR_SECTION; + break; + case STATE_VALUE_OR_SECTION: + if (strneq(line, "Match", 5)) { + qlog_parser(ctx, "%s:%d: expected value or [Section], have %s\n", + path, lineno, line); + goto out; + } + break; + case STATE_ANY: + break; + } + + if (!parse_value_line(ctx, section, line)) { + qlog_parser(ctx, "%s:%d: failed to parse %s\n", + path, lineno, line); + goto out; + } + break; + default: + qlog_parser(ctx, "%s:%d: Unexpected line %s\n", + path, lineno, line); + goto out; + } + } + + if (!section) { + qlog_parser(ctx, "%s: is an empty file\n", path); + goto out; + } + + if ((!section->has_match || !section->has_property)) { + qlog_parser(ctx, "%s:%d: previous section %s was empty\n", + path, lineno, section->name); + goto out; /* Previous section was empty */ + } + + rc = true; +out: + if (fp) + fclose(fp); + + return rc; +} + +static int +is_data_file(const struct dirent *dir) { + const char *suffix = ".quirks"; + const int slen = strlen(suffix); + int offset; + + offset = strlen(dir->d_name) - slen; + if (offset <= 0) + return 0; + + return strneq(&dir->d_name[offset], suffix, slen); +} + +static inline bool +parse_files(struct quirks_context *ctx, const char *data_path) +{ + struct dirent **namelist; + int ndev = -1; + int idx = 0; + + ndev = scandir(data_path, &namelist, is_data_file, versionsort); + if (ndev <= 0) { + qlog_error(ctx, + "%s: failed to find data files\n", + data_path); + return false; + } + + for (idx = 0; idx < ndev; idx++) { + char path[PATH_MAX]; + + snprintf(path, + sizeof(path), + "%s/%s", + data_path, + namelist[idx]->d_name); + + if (!parse_file(ctx, path)) + break; + } + + for (int i = 0; i < ndev; i++) + free(namelist[i]); + free(namelist); + + return idx == ndev; +} + +struct quirks_context * +quirks_init_subsystem(const char *data_path, + const char *override_file, + libinput_log_handler log_handler, + struct libinput *libinput, + enum quirks_log_type log_type) +{ + struct quirks_context *ctx = zalloc(sizeof *ctx); + + ctx->refcount = 1; + ctx->log_handler = log_handler; + ctx->log_type = log_type; + ctx->libinput = libinput; + list_init(&ctx->quirks); + list_init(&ctx->sections); + + qlog_debug(ctx, "%s is data root\n", data_path); + + ctx->dmi = init_dmi(); + if (!ctx->dmi) + goto error; + + if (!parse_files(ctx, data_path)) + goto error; + + if (override_file && !parse_file(ctx, override_file)) + goto error; + + return ctx; + +error: + quirks_context_unref(ctx); + return NULL; +} + +struct quirks_context * +quirks_context_ref(struct quirks_context *ctx) +{ + assert(ctx->refcount > 0); + ctx->refcount++; + + return ctx; +} + +struct quirks_context * +quirks_context_unref(struct quirks_context *ctx) +{ + struct section *s, *tmp; + + if (!ctx) + return NULL; + + assert(ctx->refcount >= 1); + ctx->refcount--; + + if (ctx->refcount > 0) + return NULL; + + /* Caller needs to clean up before calling this */ + assert(list_empty(&ctx->quirks)); + + list_for_each_safe(s, tmp, &ctx->sections, link) { + section_destroy(s); + } + + free(ctx->dmi); + free(ctx); + + return NULL; +} + +static struct quirks * +quirks_new(void) +{ + struct quirks *q; + + q = zalloc(sizeof *q); + q->refcount = 1; + q->nproperties = 0; + list_init(&q->link); + + return q; +} + +struct quirks * +quirks_unref(struct quirks *q) +{ + struct property *p; + + if (!q) + return NULL; + + /* We don't really refcount, but might + * as well have the API in place */ + assert(q->refcount == 1); + + for (size_t i = 0; i < q->nproperties; i++) { + property_unref(q->properties[i]); + p++; + } + + list_remove(&q->link); + free(q->properties); + free(q); + + return NULL; +} + +/** + * Searches for the udev property on this device and its parent devices. + * + * @return the value of the property or NULL + */ +static const char * +udev_prop(struct udev_device *device, const char *prop) +{ + struct udev_device *d = device; + const char *value = NULL; + + do { + value = udev_device_get_property_value(d, prop); + d = udev_device_get_parent(d); + } while (value == NULL && d != NULL); + + return value; +} + +static inline void +match_fill_name(struct match *m, + struct udev_device *device) +{ + const char *str = udev_prop(device, "NAME"); + size_t slen; + + if (!str) + return; + + /* udev NAME is in quotes, strip them */ + if (str[0] == '"') + str++; + + m->name = safe_strdup(str); + slen = strlen(m->name); + if (slen > 1 && + m->name[slen - 1] == '"') + m->name[slen - 1] = '\0'; + + m->bits |= M_NAME; +} + +static inline void +match_fill_bus_vid_pid(struct match *m, + struct udev_device *device) +{ + const char *str; + unsigned int product, vendor, bus, version; + + str = udev_prop(device, "PRODUCT"); + if (!str) + return; + + /* ID_VENDOR_ID/ID_PRODUCT_ID/ID_BUS aren't filled in for virtual + * devices so we have to resort to PRODUCT */ + if (sscanf(str, "%x/%x/%x/%x", &bus, &vendor, &product, &version) != 4) + return; + + m->product = product; + m->vendor = vendor; + m->bits |= M_PID|M_VID; + switch (bus) { + case BUS_USB: + m->bus = BT_USB; + m->bits |= M_BUS; + break; + case BUS_BLUETOOTH: + m->bus = BT_BLUETOOTH; + m->bits |= M_BUS; + break; + case BUS_I8042: + m->bus = BT_PS2; + m->bits |= M_BUS; + break; + default: + break; + } +} + +static inline void +match_fill_udev_type(struct match *m, + struct udev_device *device) +{ + struct ut_map { + const char *prop; + enum udev_type flag; + } mappings[] = { + { "ID_INPUT_MOUSE", UDEV_MOUSE }, + { "ID_INPUT_POINTINGSTICK", UDEV_POINTINGSTICK }, + { "ID_INPUT_TOUCHPAD", UDEV_TOUCHPAD }, + { "ID_INPUT_TABLET", UDEV_TABLET }, + { "ID_INPUT_TABLET_PAD", UDEV_TABLET_PAD }, + { "ID_INPUT_JOYSTICK", UDEV_JOYSTICK }, + { "ID_INPUT_KEYBOARD", UDEV_KEYBOARD }, + }; + struct ut_map *map; + + ARRAY_FOR_EACH(mappings, map) { + if (udev_prop(device, map->prop)) + m->udev_type |= map->flag; + } + m->bits |= M_UDEV_TYPE; +} + +static inline void +match_fill_dmi(struct match *m, + char *dmi) +{ + m->dmi = dmi; + m->bits |= M_DMI; +} + +static struct match * +match_new(struct udev_device *device, + char *dmi) +{ + struct match *m = zalloc(sizeof *m); + + match_fill_name(m, device); + match_fill_bus_vid_pid(m, device); + match_fill_dmi(m, dmi); + match_fill_udev_type(m, device); + /* FIXME: dt */ + return m; +} + +static void +match_free(struct match *m) +{ + free(m->name); + free(m->dt); + free(m); +} + +static void +quirk_apply_section(struct quirks_context *ctx, + struct quirks *q, + const struct section *s) +{ + struct property *p; + size_t nprops = 0; + void *tmp; + + list_for_each(p, &s->properties, link) { + nprops++; + } + + nprops += q->nproperties; + tmp = reallocarray(q->properties, nprops, sizeof(p)); + if (!tmp) + return; + + q->properties = tmp; + list_for_each(p, &s->properties, link) { + qlog_debug(ctx, "property added: %s from %s\n", + quirk_get_name(p->id), s->name); + + q->properties[q->nproperties++] = property_ref(p); + } +} + +static bool +quirk_match_section(struct quirks_context *ctx, + struct quirks *q, + struct section *s, + struct match *m, + struct udev_device *device) +{ + uint32_t matched_flags = 0x0; + + for (uint32_t flag = 0x1; flag <= M_LAST; flag <<= 1) { + uint32_t prev_matched_flags = matched_flags; + /* section doesn't have this bit set, continue */ + if ((s->match.bits & flag) == 0) + continue; + + /* Couldn't fill in this bit for the match, so we + * do not match on it */ + if ((m->bits & flag) == 0) { + qlog_debug(ctx, + "%s wants %s but we don't have that\n", + s->name, matchflagname(flag)); + continue; + } + + /* now check the actual matching bit */ + switch (flag) { + case M_NAME: + if (fnmatch(s->match.name, m->name, 0) == 0) + matched_flags |= flag; + break; + case M_BUS: + if (m->bus == s->match.bus) + matched_flags |= flag; + break; + case M_VID: + if (m->vendor == s->match.vendor) + matched_flags |= flag; + break; + case M_PID: + if (m->product == s->match.product) + matched_flags |= flag; + break; + case M_DMI: + if (fnmatch(s->match.dmi, m->dmi, 0) == 0) + matched_flags |= flag; + break; + case M_UDEV_TYPE: + if (s->match.udev_type & m->udev_type) + matched_flags |= flag; + break; + case M_DT: + /* FIXME */ + break; + default: + abort(); + } + + if (prev_matched_flags != matched_flags) { + qlog_debug(ctx, + "%s matches for %s\n", + s->name, + matchflagname(flag)); + } + } + + if (s->match.bits == matched_flags) { + qlog_debug(ctx, "%s is full match\n", s->name); + quirk_apply_section(ctx, q, s); + } + + return true; +} + +struct quirks * +quirks_fetch_for_device(struct quirks_context *ctx, + struct udev_device *udev_device) +{ + struct quirks *q = NULL; + struct section *s; + struct match *m; + + if (!ctx) + return NULL; + + qlog_debug(ctx, "%s: fetching quirks\n", + udev_device_get_devnode(udev_device)); + + q = quirks_new(); + + m = match_new(udev_device, ctx->dmi); + + list_for_each(s, &ctx->sections, link) { + quirk_match_section(ctx, q, s, m, udev_device); + } + + match_free(m); + + if (q->nproperties == 0) { + quirks_unref(q); + return NULL; + } + + list_insert(&ctx->quirks, &q->link); + + return q; +} + + +static inline struct property * +quirk_find_prop(struct quirks *q, enum quirk which) +{ + /* Run backwards to only handle the last one assigned */ + for (ssize_t i = q->nproperties - 1; i >= 0; i--) { + struct property *p = q->properties[i]; + if (p->id == which) + return p; + } + + return NULL; +} + +bool +quirks_has_quirk(struct quirks *q, enum quirk which) +{ + return quirk_find_prop(q, which) != NULL; +} + +bool +quirks_get_int32(struct quirks *q, enum quirk which, int32_t *val) +{ + struct property *p; + + if (!q) + return false; + + p = quirk_find_prop(q, which); + if (!p) + return false; + + assert(p->type == PT_INT); + *val = p->value.i; + + return true; +} + +bool +quirks_get_uint32(struct quirks *q, enum quirk which, uint32_t *val) +{ + struct property *p; + + if (!q) + return false; + + p = quirk_find_prop(q, which); + if (!p) + return false; + + assert(p->type == PT_UINT); + *val = p->value.u; + + return true; +} + +bool +quirks_get_string(struct quirks *q, enum quirk which, char **val) +{ + struct property *p; + + if (!q) + return false; + + p = quirk_find_prop(q, which); + if (!p) + return false; + + assert(p->type == PT_STRING); + *val = p->value.s; + + return true; +} + +bool +quirks_get_bool(struct quirks *q, enum quirk which, bool *val) +{ + struct property *p; + + if (!q) + return false; + + p = quirk_find_prop(q, which); + if (!p) + return false; + + assert(p->type == PT_BOOL); + *val = p->value.b; + + return true; +} + +bool +quirks_get_dimensions(struct quirks *q, + enum quirk which, + struct quirk_dimensions *val) +{ + struct property *p; + + if (!q) + return false; + + p = quirk_find_prop(q, which); + if (!p) + return false; + + assert(p->type == PT_DIMENSION); + *val = p->value.dim; + + return true; +} + +bool +quirks_get_range(struct quirks *q, + enum quirk which, + struct quirk_range *val) +{ + struct property *p; + + if (!q) + return false; + + p = quirk_find_prop(q, which); + if (!p) + return false; + + assert(p->type == PT_RANGE); + *val = p->value.range; + + return true; +} diff --git a/src/quirks.h b/src/quirks.h new file mode 100644 index 00000000..c7c0a09c --- /dev/null +++ b/src/quirks.h @@ -0,0 +1,273 @@ +/* + * Copyright © 2018 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#pragma once + +#include "config.h" + +#include <stdbool.h> +#include <stdint.h> + +#include <libudev.h> + +#include "libinput.h" + +/** + * Handle to the quirks context. + */ +struct quirks_context; + +/** + * Contains all quirks set for a single device. + */ +struct quirks; + +struct quirk_dimensions { + size_t x, y; +}; + +struct quirk_range { + int lower, upper; +}; + +/** + * Quirks known to libinput + */ +enum quirk { + QUIRK_MODEL_ALPS_TOUCHPAD = 100, + QUIRK_MODEL_APPLE_TOUCHPAD, + QUIRK_MODEL_APPLE_MAGICMOUSE, + QUIRK_MODEL_TABLET_NO_TILT, + QUIRK_MODEL_APPLE_TOUCHPAD_ONEBUTTON, + QUIRK_MODEL_TOUCHPAD_VISIBLE_MARKER, + QUIRK_MODEL_CYBORG_RAT, + QUIRK_MODEL_CHROMEBOOK, + QUIRK_MODEL_HP6910_TOUCHPAD, + QUIRK_MODEL_HP8510_TOUCHPAD, + QUIRK_MODEL_HP_PAVILION_DM4_TOUCHPAD, + QUIRK_MODEL_HP_STREAM11_TOUCHPAD, + QUIRK_MODEL_HP_ZBOOK_STUDIO_G3, + QUIRK_MODEL_TABLET_NO_PROXIMITY_OUT, + QUIRK_MODEL_LENOVO_SCROLLPOINT, + QUIRK_MODEL_LENOVO_X230, + QUIRK_MODEL_LENOVO_T450_TOUCHPAD, + QUIRK_MODEL_TABLET_MODE_NO_SUSPEND, + QUIRK_MODEL_LENOVO_CARBON_X1_6TH, + QUIRK_MODEL_TRACKBALL, + QUIRK_MODEL_LOGITECH_MARBLE_MOUSE, + QUIRK_MODEL_BOUNCING_KEYS, + QUIRK_MODEL_SYNAPTICS_SERIAL_TOUCHPAD, + QUIRK_MODEL_SYSTEM76_BONOBO, + QUIRK_MODEL_CLEVO_W740SU, + QUIRK_MODEL_SYSTEM76_GALAGO, + QUIRK_MODEL_SYSTEM76_KUDU, + QUIRK_MODEL_WACOM_TOUCHPAD, + QUIRK_MODEL_JUMPING_SEMI_MT, + + + QUIRK_ATTR_SIZE_HINT = 300, + QUIRK_ATTR_TOUCH_SIZE_RANGE, + QUIRK_ATTR_PALM_SIZE_THRESHOLD, + QUIRK_ATTR_LID_SWITCH_RELIABILITY, + QUIRK_ATTR_KEYBOARD_INTEGRATION, + QUIRK_ATTR_TPKBCOMBO_LAYOUT, + QUIRK_ATTR_PRESSURE_RANGE, + QUIRK_ATTR_PALM_PRESSURE_THRESHOLD, + QUIRK_ATTR_RESOLUTION_HINT, + QUIRK_ATTR_TRACKPOINT_RANGE, + QUIRK_ATTR_THUMB_PRESSURE_THRESHOLD, +}; + +/** + * Returns a printable name for the quirk. This name is for developer + * tools, not user consumption. Do not display this in a GUI. + */ +const char* +quirk_get_name(enum quirk which); + +/** + * Log priorities used if custom logging is enabled. + */ +enum quirks_log_priorities { + QLOG_NOISE, + QLOG_DEBUG = LIBINPUT_LOG_PRIORITY_DEBUG, + QLOG_INFO = LIBINPUT_LOG_PRIORITY_INFO, + QLOG_ERROR = LIBINPUT_LOG_PRIORITY_ERROR, + QLOG_PARSER_ERROR, +}; + +/** + * Log type to be used for logging. Use the libinput logging to hook up a + * libinput log handler. This will cause the quirks to reduce the noise and + * only provide useful messages. + * + * QLOG_CUSTOM_LOG_PRIORITIES enables more fine-grained and verbose logging, + * allowing debugging tools to be more useful. + */ +enum quirks_log_type { + QLOG_LIBINPUT_LOGGING, + QLOG_CUSTOM_LOG_PRIORITIES, +}; + +/** + * Initialize the quirks subsystem. This function must be called + * before anything else. + * + * If log_type is QLOG_CUSTOM_LOG_PRIORITIES, the log handler is called with + * the custom QLOG_* log priorities. Otherwise, the log handler only uses + * the libinput log priorities. + * + * @param data_path The directory containing the various data files + * @param override_file A file path containing custom overrides + * @param log_handler The libinput log handler called for debugging output + * @param libinput The libinput struct passed to the log handler + * + * @return an opaque handle to the context + */ +struct quirks_context * +quirks_init_subsystem(const char *data_path, + const char *override_file, + libinput_log_handler log_handler, + struct libinput *libinput, + enum quirks_log_type log_type); + +/** + * Clean up after ourselves. This function must be called + * as the last call to the quirks subsystem. + * + * All quirks returned to the caller in quirks_fetch_for_device() must be + * unref'd before this call. + * + * @return Always NULL + */ +struct quirks_context * +quirks_context_unref(struct quirks_context *ctx); + +struct quirks_context * +quirks_context_ref(struct quirks_context *ctx); + +/** + * Fetch the quirks for a given device. If no quirks are defined, this + * function returns NULL. + * + * @return A new quirks struct, use quirks_unref() to release + */ +struct quirks * +quirks_fetch_for_device(struct quirks_context *ctx, + struct udev_device *device); + +/** + * Reduce the refcount by one. When the refcount reaches zero, the + * associated struct is released. + * + * @return Always NULL + */ +struct quirks * +quirks_unref(struct quirks *q); + +/** + * Returns true if the given quirk applies is in this quirk list. + */ +bool +quirks_has_quirk(struct quirks *q, enum quirk which); + +/** + * Get the value of the given quirk, as unsigned integer. + * This function will assert if the quirk type does not match the + * requested type. If the quirk is not set for this device, val is + * unchanged. + * + * @return true if the quirk value is valid, false otherwise. + */ +bool +quirks_get_uint32(struct quirks *q, + enum quirk which, + uint32_t *val); + +/** + * Get the value of the given quirk, as signed integer. + * This function will assert if the quirk type does not match the + * requested type. If the quirk is not set for this device, val is + * unchanged. + * + * @return true if the quirk value is valid, false otherwise. + */ +bool +quirks_get_int32(struct quirks *q, + enum quirk which, + int32_t *val); + +/** + * Get the value of the given quirk, as string. + * This function will assert if the quirk type does not match the + * requested type. If the quirk is not set for this device, val is + * unchanged. + * + * val is set to the string, do not modify or free it. The lifetime of the + * returned string is bound to the lifetime of the quirk. + * + * @return true if the quirk value is valid, false otherwise. + */ +bool +quirks_get_string(struct quirks *q, + enum quirk which, + char **val); + +/** + * Get the value of the given quirk, as bool. + * This function will assert if the quirk type does not match the + * requested type. If the quirk is not set for this device, val is + * unchanged. + * + * @return true if the quirk value is valid, false otherwise. + */ +bool +quirks_get_bool(struct quirks *q, + enum quirk which, + bool *val); + +/** + * Get the value of the given quirk, as dimension. + * This function will assert if the quirk type does not match the + * requested type. If the quirk is not set for this device, val is + * unchanged. + * + * @return true if the quirk value is valid, false otherwise. + */ +bool +quirks_get_dimensions(struct quirks *q, + enum quirk which, + struct quirk_dimensions *val); + +/** + * Get the value of the given quirk, as range. + * This function will assert if the quirk type does not match the + * requested type. If the quirk is not set for this device, val is + * unchanged. + * + * @return true if the quirk value is valid, false otherwise. + */ +bool +quirks_get_range(struct quirks *q, + enum quirk which, + struct quirk_range *val); diff --git a/test/test-quirks.c b/test/test-quirks.c new file mode 100644 index 00000000..646559ed --- /dev/null +++ b/test/test-quirks.c @@ -0,0 +1,809 @@ +/* + * Copyright © 2018 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <config.h> + +#include <check.h> +#include <libinput.h> + +#include "libinput-util.h" +#include "litest.h" +#include "quirks.h" + +static void +log_handler(struct libinput *this_is_null, + enum libinput_log_priority priority, + const char *format, + va_list args) +{ +#if 0 + vprintf(format, args); +#endif +} + +struct data_dir { + char *dirname; + char *filename; +}; + +static struct data_dir +make_data_dir(const char *file_content) +{ + struct data_dir dir = {0}; + char dirname[PATH_MAX] = "/run/litest-quirk-test-XXXXXX"; + char *filename; + FILE *fp; + int rc; + + litest_assert_notnull(mkdtemp(dirname)); + dir.dirname = safe_strdup(dirname); + + if (file_content) { + rc = xasprintf(&filename, "%s/testfile.quirks", dirname); + litest_assert_int_eq(rc, (int)(strlen(dirname) + 16)); + + fp = fopen(filename, "w+"); + rc = fputs(file_content, fp); + fclose(fp); + litest_assert_int_ge(rc, 0); + dir.filename = filename; + } + + return dir; +} + +static void +cleanup_data_dir(struct data_dir dd) +{ + if (dd.filename) { + unlink(dd.filename); + free(dd.filename); + } + if (dd.dirname) { + rmdir(dd.dirname); + free(dd.dirname); + } +} + +START_TEST(quirks_invalid_dir) +{ + struct quirks_context *ctx; + + ctx = quirks_init_subsystem("/does-not-exist", + NULL, + log_handler, + NULL, + QLOG_LIBINPUT_LOGGING); + ck_assert(ctx == NULL); +} +END_TEST + +START_TEST(quirks_empty_dir) +{ + struct quirks_context *ctx; + struct data_dir dd = make_data_dir(NULL); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_LIBINPUT_LOGGING); + ck_assert(ctx == NULL); + + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_section_empty) +{ + struct quirks_context *ctx; + const char quirks_file[] = "[Empty Section]"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_section_double) +{ + struct quirks_context *ctx; + const char quirks_file[] = "[Section name]"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_section_missing_match) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "AttrSizeHint=10x10\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_section_missing_attr) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchUdevType=mouse\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_section_match_after_attr) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchUdevType=mouse\n" + "AttrSizeHint=10x10\n" + "MatchName=mouse\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_section_duplicate_match) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchUdevType=mouse\n" + "MatchUdevType=mouse\n" + "AttrSizeHint=10x10\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_section_duplicate_attr) +{ + /* This shouldn't be allowed but the current parser + is happy with it */ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchUdevType=mouse\n" + "AttrSizeHint=10x10\n" + "AttrSizeHint=10x10\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert_notnull(ctx); + quirks_context_unref(ctx); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_parse_error_section) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section Missing Bracket\n" + "MatchUdevType=mouse\n" + "AttrSizeHint=10x10\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_parse_error_unknown_match) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "Matchblahblah=mouse\n" + "AttrSizeHint=10x10\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_parse_error_unknown_attr) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchUdevType=mouse\n" + "Attrblahblah=10x10\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_parse_error_unknown_model) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchUdevType=mouse\n" + "Modelblahblah=1\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_parse_error_model_not_one) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchUdevType=mouse\n" + "ModelAppleTouchpad=true\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_parse_bustype) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchBus=usb\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchBus=bluetooth\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchBus=i2c\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchBus=rmi\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchBus=ps2\n" + "ModelAppleTouchpad=1\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert_notnull(ctx); + quirks_context_unref(ctx); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_parse_bustype_invalid) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchBustype=venga\n" + "ModelAppleTouchpad=1\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_parse_vendor) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchVendor=0x0000\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchVendor=0x0001\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchVendor=0x2343\n" + "ModelAppleTouchpad=1\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert_notnull(ctx); + quirks_context_unref(ctx); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_parse_vendor_invalid) +{ + struct quirks_context *ctx; + const char *quirks_file[] = { + "[Section name]\n" + "MatchVendor=-1\n" + "ModelAppleTouchpad=1\n", + "[Section name]\n" + "MatchVendor=abc\n" + "ModelAppleTouchpad=1\n", + "[Section name]\n" + "MatchVendor=0xFFFFF\n" + "ModelAppleTouchpad=1\n", + "[Section name]\n" + "MatchVendor=123\n" + "ModelAppleTouchpad=1\n", + }; + const char **qf; + + ARRAY_FOR_EACH(quirks_file, qf) { + struct data_dir dd = make_data_dir(*qf); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); + } +} +END_TEST + +START_TEST(quirks_parse_product) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchProduct=0x0000\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchProduct=0x0001\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchProduct=0x2343\n" + "ModelAppleTouchpad=1\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert_notnull(ctx); + quirks_context_unref(ctx); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_parse_product_invalid) +{ + struct quirks_context *ctx; + const char *quirks_file[] = { + "[Section name]\n" + "MatchProduct=-1\n" + "ModelAppleTouchpad=1\n", + "[Section name]\n" + "MatchProduct=abc\n" + "ModelAppleTouchpad=1\n", + "[Section name]\n" + "MatchProduct=0xFFFFF\n" + "ModelAppleTouchpad=1\n", + "[Section name]\n" + "MatchProduct=123\n" + "ModelAppleTouchpad=1\n", + }; + const char **qf; + + ARRAY_FOR_EACH(quirks_file, qf) { + struct data_dir dd = make_data_dir(*qf); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); + } +} +END_TEST + +START_TEST(quirks_parse_name) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchName=1235\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchName=abc\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchName=*foo\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchName=foo*\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchName=foo[]\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchName=*foo*\n" + "ModelAppleTouchpad=1\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert_notnull(ctx); + quirks_context_unref(ctx); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_parse_name_invalid) +{ + struct quirks_context *ctx; + const char *quirks_file[] = { + "[Section name]\n" + "MatchName=\n" + "ModelAppleTouchpad=1\n", + }; + const char **qf; + + ARRAY_FOR_EACH(quirks_file, qf) { + struct data_dir dd = make_data_dir(*qf); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); + } +} +END_TEST + +START_TEST(quirks_parse_udev) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchUdevType=touchpad\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchUdevType=mouse\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchUdevType=pointingstick\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchUdevType=tablet\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchUdevType=tablet-pad\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchUdevType=keyboard\n" + "ModelAppleTouchpad=1\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert_notnull(ctx); + quirks_context_unref(ctx); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_parse_udev_invalid) +{ + struct quirks_context *ctx; + const char *quirks_file[] = { + "[Section name]\n" + "MatchUdevType=blah\n" + "ModelAppleTouchpad=1\n", + "[Section name]\n" + "MatchUdevType=\n" + "ModelAppleTouchpad=1\n", + "[Section name]\n" + "MatchUdevType=123\n" + "ModelAppleTouchpad=1\n", + }; + const char **qf; + + ARRAY_FOR_EACH(quirks_file, qf) { + struct data_dir dd = make_data_dir(*qf); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); + } +} +END_TEST + +START_TEST(quirks_parse_dmi) +{ + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchDMIModalias=dmi:*\n" + "ModelAppleTouchpad=1\n" + "\n" + "[Section name]\n" + "MatchDMIModalias=dmi:*svn*pn*:\n" + "ModelAppleTouchpad=1\n"; + struct data_dir dd = make_data_dir(quirks_file); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert_notnull(ctx); + quirks_context_unref(ctx); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_parse_dmi_invalid) +{ + struct quirks_context *ctx; + const char *quirks_file[] = { + "[Section name]\n" + "MatchDMIModalias=\n" + "ModelAppleTouchpad=1\n", + "[Section name]\n" + "MatchDMIModalias=*pn*\n" + "ModelAppleTouchpad=1\n", + "[Section name]\n" + "MatchDMIModalias=dmi*pn*\n" + "ModelAppleTouchpad=1\n", + "[Section name]\n" + "MatchDMIModalias=foo\n" + "ModelAppleTouchpad=1\n", + }; + const char **qf; + + ARRAY_FOR_EACH(quirks_file, qf) { + struct data_dir dd = make_data_dir(*qf); + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert(ctx == NULL); + cleanup_data_dir(dd); + } +} +END_TEST + +START_TEST(quirks_model_one) +{ + struct litest_device *dev = litest_current_device(); + struct udev_device *ud = libinput_device_get_udev_device(dev->libinput_device); + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchUdevType=mouse\n" + "ModelAppleTouchpad=1\n"; + struct data_dir dd = make_data_dir(quirks_file); + struct quirks *q; + bool isset; + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert_notnull(ctx); + + q = quirks_fetch_for_device(ctx, ud); + ck_assert_notnull(q); + + ck_assert(quirks_get_bool(q, QUIRK_MODEL_APPLE_TOUCHPAD, &isset)); + ck_assert(isset == true); + + quirks_unref(q); + quirks_context_unref(ctx); + cleanup_data_dir(dd); +} +END_TEST + +START_TEST(quirks_model_zero) +{ + struct litest_device *dev = litest_current_device(); + struct udev_device *ud = libinput_device_get_udev_device(dev->libinput_device); + struct quirks_context *ctx; + const char quirks_file[] = + "[Section name]\n" + "MatchUdevType=mouse\n" + "ModelAppleTouchpad=0\n"; + struct data_dir dd = make_data_dir(quirks_file); + struct quirks *q; + bool isset; + + ctx = quirks_init_subsystem(dd.dirname, + NULL, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + ck_assert_notnull(ctx); + + q = quirks_fetch_for_device(ctx, ud); + ck_assert_notnull(q); + + ck_assert(quirks_get_bool(q, QUIRK_MODEL_APPLE_TOUCHPAD, &isset)); + ck_assert(isset == false); + + quirks_unref(q); + quirks_context_unref(ctx); + cleanup_data_dir(dd); +} +END_TEST + +TEST_COLLECTION(quirks) +{ + litest_add_for_device("quirks:datadir", quirks_invalid_dir, LITEST_MOUSE); + litest_add_for_device("quirks:datadir", quirks_empty_dir, LITEST_MOUSE); + + litest_add_for_device("quirks:structure", quirks_section_empty, LITEST_MOUSE); + litest_add_for_device("quirks:structure", quirks_section_double, LITEST_MOUSE); + litest_add_for_device("quirks:structure", quirks_section_missing_match, LITEST_MOUSE); + litest_add_for_device("quirks:structure", quirks_section_missing_attr, LITEST_MOUSE); + litest_add_for_device("quirks:structure", quirks_section_match_after_attr, LITEST_MOUSE); + litest_add_for_device("quirks:structure", quirks_section_duplicate_match, LITEST_MOUSE); + litest_add_for_device("quirks:structure", quirks_section_duplicate_attr, LITEST_MOUSE); + + litest_add_for_device("quirks:parsing", quirks_parse_error_section, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_error_unknown_match, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_error_unknown_attr, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_error_unknown_model, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_error_model_not_one, LITEST_MOUSE); + + litest_add_for_device("quirks:parsing", quirks_parse_bustype, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_bustype_invalid, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_vendor, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_vendor_invalid, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_product, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_product_invalid, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_name, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_name_invalid, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_udev, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_udev_invalid, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_dmi, LITEST_MOUSE); + litest_add_for_device("quirks:parsing", quirks_parse_dmi_invalid, LITEST_MOUSE); + + litest_add_for_device("quirks:model", quirks_model_one, LITEST_MOUSE); + litest_add_for_device("quirks:model", quirks_model_zero, LITEST_MOUSE); +} diff --git a/tools/libinput-list-quirks.c b/tools/libinput-list-quirks.c new file mode 100644 index 00000000..73e84895 --- /dev/null +++ b/tools/libinput-list-quirks.c @@ -0,0 +1,245 @@ +/* + * Copyright © 2018 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <getopt.h> +#include <sys/stat.h> + +#include "libinput-util.h" +#include "quirks.h" + +static bool verbose = false; + +static void +log_handler(struct libinput *this_is_null, + enum libinput_log_priority priority, + const char *format, + va_list args) +{ + FILE *out = stdout; + enum quirks_log_priorities p = priority; + char buf[256] = {0}; + const char *prefix = ""; + + switch (p) { + case QLOG_NOISE: + case QLOG_DEBUG: + if (!verbose) + return; + prefix = "quirks debug"; + break; + case QLOG_INFO: + prefix = "quirks info"; + break; + case QLOG_ERROR: + out = stderr; + prefix = "quirks error"; + break; + case QLOG_PARSER_ERROR: + out = stderr; + prefix = "quirks parser error"; + break; + } + + snprintf(buf, sizeof(buf), "%s: %s", prefix, format); + vfprintf(out, buf, args); +} + +static void +list_device_quirks(struct quirks_context *ctx, struct udev_device *device) +{ + struct quirks *quirks; + + quirks = quirks_fetch_for_device(ctx, device); + if (!quirks) { + printf("Device has no quirks defined\n"); + } else { + enum quirk qlist[] = { + QUIRK_MODEL_ALPS_TOUCHPAD, + QUIRK_MODEL_APPLE_TOUCHPAD, + QUIRK_MODEL_APPLE_MAGICMOUSE, + QUIRK_MODEL_TABLET_NO_TILT, + QUIRK_MODEL_APPLE_TOUCHPAD_ONEBUTTON, + QUIRK_MODEL_TOUCHPAD_VISIBLE_MARKER, + QUIRK_MODEL_CYBORG_RAT, + QUIRK_MODEL_CHROMEBOOK, + QUIRK_MODEL_HP6910_TOUCHPAD, + QUIRK_MODEL_HP8510_TOUCHPAD, + QUIRK_MODEL_HP_PAVILION_DM4_TOUCHPAD, + QUIRK_MODEL_HP_STREAM11_TOUCHPAD, + QUIRK_MODEL_HP_ZBOOK_STUDIO_G3, + QUIRK_MODEL_TABLET_NO_PROXIMITY_OUT, + QUIRK_MODEL_LENOVO_SCROLLPOINT, + QUIRK_MODEL_LENOVO_X230, + QUIRK_MODEL_LENOVO_T450_TOUCHPAD, + QUIRK_MODEL_TABLET_MODE_NO_SUSPEND, + QUIRK_MODEL_LENOVO_CARBON_X1_6TH, + QUIRK_MODEL_TRACKBALL, + QUIRK_MODEL_LOGITECH_MARBLE_MOUSE, + QUIRK_MODEL_BOUNCING_KEYS, + QUIRK_MODEL_SYNAPTICS_SERIAL_TOUCHPAD, + QUIRK_MODEL_SYSTEM76_BONOBO, + QUIRK_MODEL_CLEVO_W740SU, + QUIRK_MODEL_SYSTEM76_GALAGO, + QUIRK_MODEL_SYSTEM76_KUDU, + QUIRK_MODEL_WACOM_TOUCHPAD, + + + QUIRK_ATTR_SIZE_HINT, + QUIRK_ATTR_TOUCH_SIZE_RANGE, + QUIRK_ATTR_PALM_SIZE_THRESHOLD, + QUIRK_ATTR_LID_SWITCH_RELIABILITY, + QUIRK_ATTR_KEYBOARD_INTEGRATION, + QUIRK_ATTR_TPKBCOMBO_LAYOUT, + QUIRK_ATTR_PRESSURE_RANGE, + QUIRK_ATTR_PALM_PRESSURE_THRESHOLD, + QUIRK_ATTR_RESOLUTION_HINT, + QUIRK_ATTR_TRACKPOINT_RANGE, + }; + enum quirk *q; + + ARRAY_FOR_EACH(qlist, q) { + if (!quirks_has_quirk(quirks, *q)) + continue; + + printf("%s\n", quirk_get_name(*q)); + } + } + + quirks_unref(quirks); +} + +static void +usage(void) +{ + printf("Usage: %s [--data-dir /path/to/data/dir] /dev/input/event0\n", + program_invocation_short_name); + printf("Note: this tool also takes a syspath\n"); +} + +int +main(int argc, char **argv) +{ + struct udev *udev; + struct udev_device *device = NULL; + const char *path; + const char *data_path = NULL, + *override_file = NULL; + int rc = 1; + struct quirks_context *quirks; + + while (1) { + int c; + int option_index = 0; + enum { + OPT_VERBOSE, + OPT_DATADIR, + }; + static struct option opts[] = { + { "help", no_argument, 0, 'h' }, + { "verbose", no_argument, 0, OPT_VERBOSE }, + { "data-dir", required_argument, 0, OPT_DATADIR }, + { 0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "h", opts, &option_index); + if (c == -1) + break; + + switch(c) { + case '?': + exit(1); + break; + case 'h': + usage(); + exit(0); + break; + case OPT_VERBOSE: + verbose = true; + break; + case OPT_DATADIR: + data_path = optarg; + break; + default: + usage(); + return 1; + } + } + + if (optind >= argc) { + usage(); + return 1; + } + + /* Overriding the data dir means no custom override file */ + if (!data_path) { + data_path = LIBINPUT_DATA_DIR; + override_file = LIBINPUT_DATA_OVERRIDE_FILE; + } + + quirks = quirks_init_subsystem(data_path, + override_file, + log_handler, + NULL, + QLOG_CUSTOM_LOG_PRIORITIES); + if (!quirks) { + fprintf(stderr, + "Failed to initialize the device quirks. " + "Please see the above errors " + "and/or re-run with --verbose for more details\n"); + return 1; + } + + udev = udev_new(); + path = argv[optind]; + if (strneq(path, "/sys/", 5)) { + device = udev_device_new_from_syspath(udev, path); + } else { + struct stat st; + if (stat(path, &st) < 0) { + fprintf(stderr, "Error: %s: %m\n", path); + goto out; + } + + device = udev_device_new_from_devnum(udev, 'c', st.st_rdev); + } + if (device) { + list_device_quirks(quirks, device); + rc = 0; + } else { + usage(); + rc = 1; + } + + udev_device_unref(device); +out: + udev_unref(udev); + + quirks_context_unref(quirks); + + return rc; +} diff --git a/tools/libinput-list-quirks.man b/tools/libinput-list-quirks.man new file mode 100644 index 00000000..9d90847e --- /dev/null +++ b/tools/libinput-list-quirks.man @@ -0,0 +1,28 @@ +.TH libinput-list-quirks "1" "" "libinput @LIBINPUT_VERSION@" "libinput Manual" +.SH NAME +libinput\-list\-quirks \- quirk debug helper for libinput +.SH SYNOPSIS +.B libinput list\-quirks [\-\-help] [\-\-data\-dir /path/to/dir] [\-\-verbose\fB] \fI/dev/input/event0\fB +.SH DESCRIPTION +.PP +The +.B "libinput list\-quirks" +tool parses the quirks file in \fIdata\-dir\fR and prints all quirks applied +to the given device. +.PP +This is a debugging tool only, its output and behavior may change at any +time. Do not rely on the output. +.SH OPTIONS +.TP 8 +.B \-\-data\-dir \fI/path/to/dir\fR +Use the given directory as data directory for quirks files. +.TP 8 +.B \-\-help +Print help +.TP 8 +.B \-\-verbose +Use verbose output, useful for debugging. +.SH LIBINPUT +Part of the +.B libinput(1) +suite |