#!/usr/bin/env python3

# omarchy:summary=Run the Synaptics touchpad haptic feedback daemon

"""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"
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:
                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 "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()
