summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/10-generic-keyboard.quirks4
-rw-r--r--data/10-generic-lid.quirks9
-rw-r--r--data/10-generic-trackball.quirks3
-rw-r--r--data/30-vendor-aiptek.quirks5
-rw-r--r--data/30-vendor-alps.quirks9
-rw-r--r--data/30-vendor-cyapa.quirks3
-rw-r--r--data/30-vendor-elantech.quirks4
-rw-r--r--data/30-vendor-huion.quirks12
-rw-r--r--data/30-vendor-ibm.quirks39
-rw-r--r--data/30-vendor-logitech.quirks44
-rw-r--r--data/30-vendor-microsoft.quirks16
-rw-r--r--data/30-vendor-razer.quirks11
-rw-r--r--data/30-vendor-synaptics.quirks6
-rw-r--r--data/30-vendor-wacom.quirks12
-rw-r--r--data/50-system-apple.quirks48
-rw-r--r--data/50-system-asus.quirks9
-rw-r--r--data/50-system-chicony.quirks7
-rw-r--r--data/50-system-cyborg.quirks6
-rw-r--r--data/50-system-dell.quirks15
-rw-r--r--data/50-system-google.quirks86
-rw-r--r--data/50-system-hp.quirks24
-rw-r--r--data/50-system-lenovo.quirks78
-rw-r--r--data/50-system-system76.quirks19
-rw-r--r--data/README.md78
-rw-r--r--doc/device-quirks.dox102
-rw-r--r--doc/page-hierarchy.dox1
-rw-r--r--meson.build72
-rw-r--r--src/quirks.c1437
-rw-r--r--src/quirks.h273
-rw-r--r--test/test-quirks.c809
-rw-r--r--tools/libinput-list-quirks.c245
-rw-r--r--tools/libinput-list-quirks.man28
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, &section->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