96 lines
2.9 KiB
Python
Executable File
96 lines
2.9 KiB
Python
Executable File
#!/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()
|