New solution to haptic touchpad

This commit is contained in:
David Heiemeier Hansson
2026-03-26 11:08:44 +01:00
parent e7f8826dea
commit 15538c4f5a
2 changed files with 111 additions and 12 deletions
+95
View File
@@ -0,0 +1,95 @@
#!/usr/bin/env python3
"""Haptic feedback daemon for Synaptics touchpads with Manual Trigger.
Monitors touchpad button press events and sends haptic pulses via HID
feature reports. Required because the kernel's HID haptic subsystem only
supports Auto Trigger with waveform enumeration, not the simpler Manual
Trigger protocol used by these Synaptics touchpads.
"""
import fcntl, glob, os, struct, sys
VENDOR = "06CB"
PRODUCT = "D01A"
REPORT_ID = 0x37
INTENSITY = 40 # 0-100
# input_event: struct timeval (16 bytes on 64-bit) + type(H) + code(H) + value(i)
EVENT_FORMAT = "llHHi"
EVENT_SIZE = struct.calcsize(EVENT_FORMAT)
EV_KEY = 0x01
BTN_LEFT = 272
BTN_RIGHT = 273
BTN_MIDDLE = 274
# ioctl: HIDIOCSFEATURE(len) = _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x06, len)
def HIDIOCSFEATURE(length):
return 0xC0000000 | (length << 16) | (ord("H") << 8) | 0x06
def find_hidraw():
for path in sorted(glob.glob("/sys/class/hidraw/hidraw*")):
uevent = os.path.join(path, "device", "uevent")
try:
with open(uevent) as f:
content = f.read().upper()
if f"0000{VENDOR}" in content and f"0000{PRODUCT}" in content:
return os.path.join("/dev", os.path.basename(path))
except OSError:
continue
return None
def find_touchpad_event():
for path in sorted(glob.glob("/sys/class/input/event*/device/name")):
try:
with open(path) as f:
name = f.read().strip().upper()
if VENDOR in name and PRODUCT in name and "TOUCHPAD" in name:
event = path.split("/")[-3]
return os.path.join("/dev/input", event)
except OSError:
continue
return None
def main():
hidraw = find_hidraw()
if not hidraw:
print("No Synaptics haptic touchpad hidraw device found", file=sys.stderr)
sys.exit(1)
event = find_touchpad_event()
if not event:
print("No Synaptics haptic touchpad input device found", file=sys.stderr)
sys.exit(1)
print(f"Haptic touchpad: hidraw={hidraw} input={event} intensity={INTENSITY}", flush=True)
haptic_report = struct.pack("BB", REPORT_ID, INTENSITY)
ioctl_req = HIDIOCSFEATURE(len(haptic_report))
hidraw_fd = os.open(hidraw, os.O_RDWR)
event_fd = os.open(event, os.O_RDONLY)
try:
while True:
data = os.read(event_fd, EVENT_SIZE)
if len(data) < EVENT_SIZE:
continue
_, _, ev_type, code, value = struct.unpack(EVENT_FORMAT, data)
if ev_type == EV_KEY and code in (BTN_LEFT, BTN_RIGHT, BTN_MIDDLE) and value == 1:
try:
fcntl.ioctl(hidraw_fd, ioctl_req, haptic_report)
except OSError:
pass
except KeyboardInterrupt:
pass
finally:
os.close(event_fd)
os.close(hidraw_fd)
if __name__ == "__main__":
main()
@@ -1,31 +1,35 @@
# Fix Dell XPS haptic touchpad losing haptic feedback after suspend/resume. # Fix Dell XPS haptic touchpad.
# The I2C controller's runtime power management aggressively suspends the touchpad, # The Synaptics haptic touchpad (06CB:D01A) uses the HID Manual Trigger
# and on resume the haptic engine sometimes fails to reinitialize. # protocol, but the kernel's HID haptic subsystem only supports Auto Trigger.
# This udev rule keeps the I2C controller always on to prevent that. # This sets up a lightweight daemon that monitors touchpad button events and
# Applies to any Dell XPS with the Synaptics haptic touchpad (06CB:D01A). # sends haptic pulses via HID feature reports on the hidraw device.
# Also disables I2C runtime PM to prevent the haptic engine losing state
# across suspend/resume.
if omarchy-hw-match "XPS" \ if omarchy-hw-match "XPS" \
&& ls /sys/bus/i2c/devices/i2c-VEN_06CB:00 2>/dev/null; then && ls /sys/bus/i2c/devices/i2c-VEN_06CB:00 2>/dev/null; then
# Disable runtime PM for I2C controller so haptic state isn't lost # Keep I2C controller power on to prevent haptic engine losing state
sudo tee /etc/udev/rules.d/99-dell-xps-haptic-touchpad.rules << 'EOF' sudo tee /etc/udev/rules.d/99-dell-xps-haptic-touchpad.rules << 'EOF'
ACTION=="add", SUBSYSTEM=="pci", KERNEL=="0000:00:19.0", ATTR{power/control}="on" ACTION=="add", SUBSYSTEM=="pci", KERNEL=="0000:00:19.0", ATTR{power/control}="on"
ACTION=="add", SUBSYSTEM=="platform", KERNEL=="i2c_designware.0", ATTR{power/control}="on" ACTION=="add", SUBSYSTEM=="platform", KERNEL=="i2c_designware.0", ATTR{power/control}="on"
EOF EOF
sudo udevadm control --reload-rules sudo udevadm control --reload-rules
# Rebind the I2C HID touchpad on boot and resume to reinitialize haptic engine # Haptic feedback daemon as a systemd service
sudo tee /etc/systemd/system/dell-xps-haptic-touchpad.service << 'SVC' sudo tee /etc/systemd/system/dell-xps-haptic-touchpad.service << SVC
[Unit] [Unit]
Description=Rebind Dell XPS haptic touchpad Description=Dell XPS haptic touchpad feedback
After=systemd-udev-settle.service After=systemd-udev-settle.service
[Service] [Service]
Type=oneshot Type=simple
ExecStart=/bin/bash -c 'if [[ -d /sys/bus/i2c/devices/i2c-VEN_06CB:00 ]]; then echo "i2c-VEN_06CB:00" | tee /sys/bus/i2c/drivers/i2c_hid_acpi/unbind > /dev/null 2>&1; sleep 1; echo "i2c-VEN_06CB:00" | tee /sys/bus/i2c/drivers/i2c_hid_acpi/bind > /dev/null 2>&1; fi' ExecStart=$OMARCHY_PATH/bin/omarchy-haptic-touchpad
Restart=on-failure
RestartSec=2
[Install] [Install]
WantedBy=multi-user.target suspend.target hibernate.target WantedBy=multi-user.target
SVC SVC
sudo systemctl daemon-reload sudo systemctl daemon-reload
sudo systemctl enable dell-xps-haptic-touchpad.service sudo systemctl enable dell-xps-haptic-touchpad.service