#!/usr/bin/env python3
"""Снимка на состојбата на Linux evdev само за читање. Без ресетирање, преземање на внесот, калибрација или запишување.

Потребни се Python 3, општата ioctl-структура на Linux (на пр. x86/ARM) и дозвола
за читање /dev/input/event*. Контактите од тип B се читаат од состојбата на јадрото.
Необработените оски се пред ротацијата/калибрацијата од композиторот. Повторете за споредба.
"""
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('\nУред:', path, name)
            print('Само моментална снимка; координатите се необработени оски на сензорот, а не пиксели на екранот.')
            if multi:
                count = absinfo(fd, 47)[2] + 1
                if not 1 <= count <= 256:
                    print('Неподдржан број слотови:', 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('Активни контакти:', len(active))
                for i in active:
                    print('Слот', i, 'ID', tracking[i], 'X=', xs[i], position(xs[i], xr),
                          'Y=', ys[i], position(ys[i], yr))
            else:
                print('Нема број на контакти од тип B; само состојба на единечен допир.')
                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, 'Не може да се прочита овој уред:', error)
        finally:
            os.close(fd)
    if not found:
        print('Не е прочитан поддржан кандидат. Ова не докажува хардверски дефект.')
    if denied:
        print('Одбиен пристап за', denied, 'влезни уреди. Обратете се до овластената поддршка.')

if __name__ == '__main__':
    main()
