#!/usr/bin/env python3
"""Linux-evdev-Zustandsabzug mit reinem Lesezugriff. Kein Reset, Grab, Kalibrieren oder Schreiben.

Benötigt Python 3, das generische Linux-ioctl-Layout (z. B. x86/ARM) und Leseberechtigung
für /dev/input/event*. Typ-B-Kontakte werden aus dem Kernelzustand abgefragt.
Rohe Achsen vor Rotation/Kalibrierung des Compositors. Zum Vergleich erneut ausführen.
"""
import array
import fcntl
import glob
import os

def query(fd, number, size, initial=None):
    data = bytearray(size) if initial is None else bytearray(initial)
    fcntl.ioctl(fd, (2 << 30) | (size << 16) | (ord('E') << 8) | number, data, True)
    return data

def absinfo(fd, code):
    a = array.array('i')
    a.frombytes(query(fd, 0x40 + code, 24))
    return a

def slots(fd, code, count):
    a = array.array('i', [code] + [0] * count)
    data = query(fd, 0x0a, len(a) * a.itemsize, a.tobytes())
    result = array.array('i'); result.frombytes(data)
    return result[1:]

def position(value, limits):
    lo, hi = limits[1], limits[2]
    return 'n/a' if hi <= lo else '{:.1f}%'.format(100 * (value - lo) / (hi - lo))

def main():
    found = denied = 0
    for path in sorted(glob.glob('/dev/input/event*')):
        try:
            fd = os.open(path, os.O_RDONLY | os.O_NONBLOCK)
        except PermissionError:
            denied += 1; continue
        except OSError as error:
            print(path, error); continue
        try:
            bits = query(fd, 0x20 + 3, 64)
            has = lambda code: bool(bits[code // 8] & (1 << (code % 8)))
            keys = query(fd, 0x20 + 1, 96)
            touch_supported = bool(keys[330 // 8] & (1 << (330 % 8)))
            multi = has(47) and has(57) and has(53) and has(54)
            if not multi and not (touch_supported and has(0) and has(1)):
                continue
            found += 1
            name = query(fd, 0x06, 256).split(b'\0', 1)[0].decode(errors='replace')
            print('\nGerät:', path, name)
            print('Nur Zustandsabzug; Koordinaten sind rohe Sensorachsen, keine Bildschirmpixel.')
            if multi:
                count = absinfo(fd, 47)[2] + 1
                if not 1 <= count <= 256:
                    print('Nicht unterstützte Slotanzahl:', count); continue
                tracking = slots(fd, 57, count)
                xs, ys = slots(fd, 53, count), slots(fd, 54, count)
                xr, yr = absinfo(fd, 53), absinfo(fd, 54)
                active = [i for i, value in enumerate(tracking) if value >= 0]
                print('Aktive Kontakte:', len(active))
                for i in active:
                    print('Slot', i, 'ID', tracking[i], 'X=', xs[i], position(xs[i], xr),
                          'Y=', ys[i], position(ys[i], yr))
            else:
                print('Keine Typ-B-Kontaktanzahl; nur Single-Touch-Zustand.')
                x, y = absinfo(fd, 0), absinfo(fd, 1)
                print('X=', x[0], position(x[0], x), 'Y=', y[0], position(y[0], y))
            if touch_supported:
                state = query(fd, 0x18, 96)
                print('BTN_TOUCH:', bool(state[330 // 8] & (1 << (330 % 8))))
        except OSError as error:
            print(path, 'Dieses Gerät kann nicht gelesen werden:', error)
        finally:
            os.close(fd)
    if not found:
        print('Kein unterstütztes Gerät ausgelesen. Dies beweist keinen Hardwaredefekt.')
    if denied:
        print('Zugriff verweigert bei', denied, 'Eingabegerät(en). Kontaktieren Sie den autorisierten Support.')

if __name__ == '__main__':
    main()
