aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/tools/testing/selftests/hid
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tools/testing/selftests/hid/.gitignore5
-rw-r--r--tools/testing/selftests/hid/Makefile241
-rw-r--r--tools/testing/selftests/hid/config32
-rw-r--r--tools/testing/selftests/hid/config.common241
-rw-r--r--tools/testing/selftests/hid/config.x86_644
-rwxr-xr-xtools/testing/selftests/hid/hid-apple.sh7
-rwxr-xr-xtools/testing/selftests/hid/hid-core.sh7
-rwxr-xr-xtools/testing/selftests/hid/hid-gamepad.sh7
-rwxr-xr-xtools/testing/selftests/hid/hid-ite.sh7
-rwxr-xr-xtools/testing/selftests/hid/hid-keyboard.sh7
-rwxr-xr-xtools/testing/selftests/hid/hid-mouse.sh7
-rwxr-xr-xtools/testing/selftests/hid/hid-multitouch.sh7
-rwxr-xr-xtools/testing/selftests/hid/hid-sony.sh7
-rwxr-xr-xtools/testing/selftests/hid/hid-tablet.sh7
-rwxr-xr-xtools/testing/selftests/hid/hid-usb_crash.sh7
-rwxr-xr-xtools/testing/selftests/hid/hid-wacom.sh7
-rw-r--r--tools/testing/selftests/hid/hid_bpf.c977
-rw-r--r--tools/testing/selftests/hid/progs/hid.c252
-rw-r--r--tools/testing/selftests/hid/progs/hid_bpf_helpers.h104
-rwxr-xr-xtools/testing/selftests/hid/run-hid-tools-tests.sh28
-rw-r--r--tools/testing/selftests/hid/settings3
-rw-r--r--tools/testing/selftests/hid/tests/__init__.py2
-rw-r--r--tools/testing/selftests/hid/tests/base.py418
-rw-r--r--tools/testing/selftests/hid/tests/base_device.py421
-rw-r--r--tools/testing/selftests/hid/tests/base_gamepad.py238
-rw-r--r--tools/testing/selftests/hid/tests/conftest.py81
-rw-r--r--tools/testing/selftests/hid/tests/descriptors_wacom.py1360
-rw-r--r--tools/testing/selftests/hid/tests/test_apple_keyboard.py440
-rw-r--r--tools/testing/selftests/hid/tests/test_gamepad.py664
-rw-r--r--tools/testing/selftests/hid/tests/test_hid_core.py154
-rw-r--r--tools/testing/selftests/hid/tests/test_ite_keyboard.py166
-rw-r--r--tools/testing/selftests/hid/tests/test_keyboard.py485
-rw-r--r--tools/testing/selftests/hid/tests/test_mouse.py977
-rw-r--r--tools/testing/selftests/hid/tests/test_multitouch.py2088
-rw-r--r--tools/testing/selftests/hid/tests/test_sony.py342
-rw-r--r--tools/testing/selftests/hid/tests/test_tablet.py1505
-rw-r--r--tools/testing/selftests/hid/tests/test_usb_crash.py103
-rw-r--r--tools/testing/selftests/hid/tests/test_wacom_generic.py1198
-rwxr-xr-xtools/testing/selftests/hid/vmtest.sh296
39 files changed, 12902 insertions, 0 deletions
diff --git a/tools/testing/selftests/hid/.gitignore b/tools/testing/selftests/hid/.gitignore
new file mode 100644
index 000000000000..995af0670f69
--- /dev/null
+++ b/tools/testing/selftests/hid/.gitignore
@@ -0,0 +1,5 @@
+bpftool
+*.skel.h
+/tools
+hid_bpf
+results
diff --git a/tools/testing/selftests/hid/Makefile b/tools/testing/selftests/hid/Makefile
new file mode 100644
index 000000000000..2b5ea18bde38
--- /dev/null
+++ b/tools/testing/selftests/hid/Makefile
@@ -0,0 +1,241 @@
+# SPDX-License-Identifier: GPL-2.0
+
+# based on tools/testing/selftest/bpf/Makefile
+include ../../../build/Build.include
+include ../../../scripts/Makefile.arch
+include ../../../scripts/Makefile.include
+
+TEST_PROGS := hid-core.sh
+TEST_PROGS += hid-apple.sh
+TEST_PROGS += hid-gamepad.sh
+TEST_PROGS += hid-ite.sh
+TEST_PROGS += hid-keyboard.sh
+TEST_PROGS += hid-mouse.sh
+TEST_PROGS += hid-multitouch.sh
+TEST_PROGS += hid-sony.sh
+TEST_PROGS += hid-tablet.sh
+TEST_PROGS += hid-usb_crash.sh
+TEST_PROGS += hid-wacom.sh
+
+CXX ?= $(CROSS_COMPILE)g++
+
+HOSTPKG_CONFIG := pkg-config
+
+CFLAGS += -g -O0 -rdynamic -Wall -Werror -I$(OUTPUT)
+CFLAGS += -I$(OUTPUT)/tools/include
+
+LDLIBS += -lelf -lz -lrt -lpthread
+
+# Silence some warnings when compiled with clang
+ifneq ($(LLVM),)
+CFLAGS += -Wno-unused-command-line-argument
+endif
+
+# Order correspond to 'make run_tests' order
+TEST_GEN_PROGS = hid_bpf
+
+# Emit succinct information message describing current building step
+# $1 - generic step name (e.g., CC, LINK, etc);
+# $2 - optional "flavor" specifier; if provided, will be emitted as [flavor];
+# $3 - target (assumed to be file); only file name will be emitted;
+# $4 - optional extra arg, emitted as-is, if provided.
+ifeq ($(V),1)
+Q =
+msg =
+else
+Q = @
+msg = @printf ' %-8s%s %s%s\n' "$(1)" "$(if $(2), [$(2)])" "$(notdir $(3))" "$(if $(4), $(4))";
+MAKEFLAGS += --no-print-directory
+submake_extras := feature_display=0
+endif
+
+# override lib.mk's default rules
+OVERRIDE_TARGETS := 1
+override define CLEAN
+ $(call msg,CLEAN)
+ $(Q)$(RM) -r $(TEST_GEN_PROGS)
+ $(Q)$(RM) -r $(EXTRA_CLEAN)
+endef
+
+include ../lib.mk
+
+TOOLSDIR := $(top_srcdir)/tools
+LIBDIR := $(TOOLSDIR)/lib
+BPFDIR := $(LIBDIR)/bpf
+TOOLSINCDIR := $(TOOLSDIR)/include
+BPFTOOLDIR := $(TOOLSDIR)/bpf/bpftool
+SCRATCH_DIR := $(OUTPUT)/tools
+BUILD_DIR := $(SCRATCH_DIR)/build
+INCLUDE_DIR := $(SCRATCH_DIR)/include
+BPFOBJ := $(BUILD_DIR)/libbpf/libbpf.a
+ifneq ($(CROSS_COMPILE),)
+HOST_BUILD_DIR := $(BUILD_DIR)/host
+HOST_SCRATCH_DIR := $(OUTPUT)/host-tools
+HOST_INCLUDE_DIR := $(HOST_SCRATCH_DIR)/include
+else
+HOST_BUILD_DIR := $(BUILD_DIR)
+HOST_SCRATCH_DIR := $(SCRATCH_DIR)
+HOST_INCLUDE_DIR := $(INCLUDE_DIR)
+endif
+HOST_BPFOBJ := $(HOST_BUILD_DIR)/libbpf/libbpf.a
+RESOLVE_BTFIDS := $(HOST_BUILD_DIR)/resolve_btfids/resolve_btfids
+
+VMLINUX_BTF_PATHS ?= $(if $(O),$(O)/vmlinux) \
+ $(if $(KBUILD_OUTPUT),$(KBUILD_OUTPUT)/vmlinux) \
+ ../../../../vmlinux \
+ /sys/kernel/btf/vmlinux \
+ /boot/vmlinux-$(shell uname -r)
+VMLINUX_BTF ?= $(abspath $(firstword $(wildcard $(VMLINUX_BTF_PATHS))))
+ifeq ($(VMLINUX_BTF),)
+$(error Cannot find a vmlinux for VMLINUX_BTF at any of "$(VMLINUX_BTF_PATHS)")
+endif
+
+# Define simple and short `make test_progs`, `make test_sysctl`, etc targets
+# to build individual tests.
+# NOTE: Semicolon at the end is critical to override lib.mk's default static
+# rule for binaries.
+$(notdir $(TEST_GEN_PROGS)): %: $(OUTPUT)/% ;
+
+# sort removes libbpf duplicates when not cross-building
+MAKE_DIRS := $(sort $(BUILD_DIR)/libbpf $(HOST_BUILD_DIR)/libbpf \
+ $(HOST_BUILD_DIR)/bpftool $(HOST_BUILD_DIR)/resolve_btfids \
+ $(INCLUDE_DIR))
+$(MAKE_DIRS):
+ $(call msg,MKDIR,,$@)
+ $(Q)mkdir -p $@
+
+# LLVM's ld.lld doesn't support all the architectures, so use it only on x86
+ifeq ($(SRCARCH),x86)
+LLD := lld
+else
+LLD := ld
+endif
+
+DEFAULT_BPFTOOL := $(HOST_SCRATCH_DIR)/sbin/bpftool
+
+TEST_GEN_PROGS_EXTENDED += $(DEFAULT_BPFTOOL)
+
+$(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED): $(BPFOBJ)
+
+BPFTOOL ?= $(DEFAULT_BPFTOOL)
+$(DEFAULT_BPFTOOL): $(wildcard $(BPFTOOLDIR)/*.[ch] $(BPFTOOLDIR)/Makefile) \
+ $(HOST_BPFOBJ) | $(HOST_BUILD_DIR)/bpftool
+ $(Q)$(MAKE) $(submake_extras) -C $(BPFTOOLDIR) \
+ ARCH= CROSS_COMPILE= CC=$(HOSTCC) LD=$(HOSTLD) \
+ EXTRA_CFLAGS='-g -O0' \
+ OUTPUT=$(HOST_BUILD_DIR)/bpftool/ \
+ LIBBPF_OUTPUT=$(HOST_BUILD_DIR)/libbpf/ \
+ LIBBPF_DESTDIR=$(HOST_SCRATCH_DIR)/ \
+ prefix= DESTDIR=$(HOST_SCRATCH_DIR)/ install-bin
+
+$(BPFOBJ): $(wildcard $(BPFDIR)/*.[ch] $(BPFDIR)/Makefile) \
+ | $(BUILD_DIR)/libbpf
+ $(Q)$(MAKE) $(submake_extras) -C $(BPFDIR) OUTPUT=$(BUILD_DIR)/libbpf/ \
+ EXTRA_CFLAGS='-g -O0' \
+ DESTDIR=$(SCRATCH_DIR) prefix= all install_headers
+
+ifneq ($(BPFOBJ),$(HOST_BPFOBJ))
+$(HOST_BPFOBJ): $(wildcard $(BPFDIR)/*.[ch] $(BPFDIR)/Makefile) \
+ | $(HOST_BUILD_DIR)/libbpf
+ $(Q)$(MAKE) $(submake_extras) -C $(BPFDIR) \
+ EXTRA_CFLAGS='-g -O0' ARCH= CROSS_COMPILE= \
+ OUTPUT=$(HOST_BUILD_DIR)/libbpf/ CC=$(HOSTCC) LD=$(HOSTLD) \
+ DESTDIR=$(HOST_SCRATCH_DIR)/ prefix= all install_headers
+endif
+
+$(INCLUDE_DIR)/vmlinux.h: $(VMLINUX_BTF) $(BPFTOOL) | $(INCLUDE_DIR)
+ifeq ($(VMLINUX_H),)
+ $(call msg,GEN,,$@)
+ $(Q)$(BPFTOOL) btf dump file $(VMLINUX_BTF) format c > $@
+else
+ $(call msg,CP,,$@)
+ $(Q)cp "$(VMLINUX_H)" $@
+endif
+
+$(RESOLVE_BTFIDS): $(HOST_BPFOBJ) | $(HOST_BUILD_DIR)/resolve_btfids \
+ $(TOOLSDIR)/bpf/resolve_btfids/main.c \
+ $(TOOLSDIR)/lib/rbtree.c \
+ $(TOOLSDIR)/lib/zalloc.c \
+ $(TOOLSDIR)/lib/string.c \
+ $(TOOLSDIR)/lib/ctype.c \
+ $(TOOLSDIR)/lib/str_error_r.c
+ $(Q)$(MAKE) $(submake_extras) -C $(TOOLSDIR)/bpf/resolve_btfids \
+ CC=$(HOSTCC) LD=$(HOSTLD) AR=$(HOSTAR) \
+ LIBBPF_INCLUDE=$(HOST_INCLUDE_DIR) \
+ OUTPUT=$(HOST_BUILD_DIR)/resolve_btfids/ BPFOBJ=$(HOST_BPFOBJ)
+
+# Get Clang's default includes on this system, as opposed to those seen by
+# '--target=bpf'. This fixes "missing" files on some architectures/distros,
+# such as asm/byteorder.h, asm/socket.h, asm/sockios.h, sys/cdefs.h etc.
+#
+# Use '-idirafter': Don't interfere with include mechanics except where the
+# build would have failed anyways.
+define get_sys_includes
+$(shell $(1) -v -E - </dev/null 2>&1 \
+ | sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }') \
+$(shell $(1) -dM -E - </dev/null | grep '__riscv_xlen ' | awk '{printf("-D__riscv_xlen=%d -D__BITS_PER_LONG=%d", $$3, $$3)}')
+endef
+
+# Determine target endianness.
+IS_LITTLE_ENDIAN = $(shell $(CC) -dM -E - </dev/null | \
+ grep 'define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__')
+MENDIAN=$(if $(IS_LITTLE_ENDIAN),-mlittle-endian,-mbig-endian)
+
+CLANG_SYS_INCLUDES = $(call get_sys_includes,$(CLANG))
+BPF_CFLAGS = -g -Werror -D__TARGET_ARCH_$(SRCARCH) $(MENDIAN) \
+ -I$(INCLUDE_DIR)
+
+CLANG_CFLAGS = $(CLANG_SYS_INCLUDES) \
+ -Wno-compare-distinct-pointer-types
+
+# Build BPF object using Clang
+# $1 - input .c file
+# $2 - output .o file
+# $3 - CFLAGS
+define CLANG_BPF_BUILD_RULE
+ $(call msg,CLNG-BPF,$(TRUNNER_BINARY),$2)
+ $(Q)$(CLANG) $3 -O2 --target=bpf -c $1 -mcpu=v3 -o $2
+endef
+# Similar to CLANG_BPF_BUILD_RULE, but with disabled alu32
+define CLANG_NOALU32_BPF_BUILD_RULE
+ $(call msg,CLNG-BPF,$(TRUNNER_BINARY),$2)
+ $(Q)$(CLANG) $3 -O2 --target=bpf -c $1 -mcpu=v2 -o $2
+endef
+# Build BPF object using GCC
+define GCC_BPF_BUILD_RULE
+ $(call msg,GCC-BPF,$(TRUNNER_BINARY),$2)
+ $(Q)$(BPF_GCC) $3 -O2 -c $1 -o $2
+endef
+
+BPF_PROGS_DIR := progs
+BPF_BUILD_RULE := CLANG_BPF_BUILD_RULE
+BPF_SRCS := $(notdir $(wildcard $(BPF_PROGS_DIR)/*.c))
+BPF_OBJS := $(patsubst %.c,$(OUTPUT)/%.bpf.o, $(BPF_SRCS))
+BPF_SKELS := $(patsubst %.c,$(OUTPUT)/%.skel.h, $(BPF_SRCS))
+TEST_GEN_FILES += $(BPF_OBJS)
+
+$(BPF_PROGS_DIR)-bpfobjs := y
+$(BPF_OBJS): $(OUTPUT)/%.bpf.o: \
+ $(BPF_PROGS_DIR)/%.c \
+ $(wildcard $(BPF_PROGS_DIR)/*.h) \
+ $(INCLUDE_DIR)/vmlinux.h \
+ $(wildcard $(BPFDIR)/hid_bpf_*.h) \
+ $(wildcard $(BPFDIR)/*.bpf.h) \
+ | $(OUTPUT) $(BPFOBJ)
+ $(call $(BPF_BUILD_RULE),$<,$@, $(BPF_CFLAGS))
+
+$(BPF_SKELS): %.skel.h: %.bpf.o $(BPFTOOL) | $(OUTPUT)
+ $(call msg,GEN-SKEL,$(BINARY),$@)
+ $(Q)$(BPFTOOL) gen object $(<:.o=.linked1.o) $<
+ $(Q)$(BPFTOOL) gen skeleton $(<:.o=.linked1.o) name $(notdir $(<:.bpf.o=)) > $@
+
+$(OUTPUT)/%.o: %.c $(BPF_SKELS)
+ $(call msg,CC,,$@)
+ $(Q)$(CC) $(CFLAGS) -c $(filter %.c,$^) $(LDLIBS) -o $@
+
+$(OUTPUT)/%: $(OUTPUT)/%.o
+ $(call msg,BINARY,,$@)
+ $(Q)$(LINK.c) $^ $(LDLIBS) -o $@
+
+EXTRA_CLEAN := $(SCRATCH_DIR) $(HOST_SCRATCH_DIR) feature bpftool \
+ $(addprefix $(OUTPUT)/,*.o *.skel.h no_alu32)
diff --git a/tools/testing/selftests/hid/config b/tools/testing/selftests/hid/config
new file mode 100644
index 000000000000..1758b055f295
--- /dev/null
+++ b/tools/testing/selftests/hid/config
@@ -0,0 +1,32 @@
+CONFIG_BPF_EVENTS=y
+CONFIG_BPF_JIT_ALWAYS_ON=y
+CONFIG_BPF_JIT=y
+CONFIG_BPF_KPROBE_OVERRIDE=y
+CONFIG_BPF_LSM=y
+CONFIG_BPF_PRELOAD_UMD=y
+CONFIG_BPF_PRELOAD=y
+CONFIG_BPF_STREAM_PARSER=y
+CONFIG_BPF_SYSCALL=y
+CONFIG_BPF=y
+CONFIG_CGROUP_BPF=y
+CONFIG_DEBUG_INFO_BTF=y
+CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS=y
+CONFIG_FPROBE=y
+CONFIG_FTRACE_SYSCALLS=y
+CONFIG_FUNCTION_TRACER=y
+CONFIG_HIDRAW=y
+CONFIG_HID=y
+CONFIG_HID_BPF=y
+CONFIG_INPUT_EVDEV=y
+CONFIG_UHID=y
+CONFIG_LEDS_CLASS_MULTICOLOR=y
+CONFIG_USB=y
+CONFIG_USB_HID=y
+CONFIG_HID_APPLE=y
+CONFIG_HID_ITE=y
+CONFIG_HID_MULTITOUCH=y
+CONFIG_HID_PLAYSTATION=y
+CONFIG_PLAYSTATION_FF=y
+CONFIG_HID_SONY=y
+CONFIG_SONY_FF=y
+CONFIG_HID_WACOM=y
diff --git a/tools/testing/selftests/hid/config.common b/tools/testing/selftests/hid/config.common
new file mode 100644
index 000000000000..45b5570441ce
--- /dev/null
+++ b/tools/testing/selftests/hid/config.common
@@ -0,0 +1,241 @@
+CONFIG_9P_FS_POSIX_ACL=y
+CONFIG_9P_FS_SECURITY=y
+CONFIG_9P_FS=y
+CONFIG_AUDIT=y
+CONFIG_BINFMT_MISC=y
+CONFIG_BLK_CGROUP_IOLATENCY=y
+CONFIG_BLK_CGROUP=y
+CONFIG_BLK_DEV_BSGLIB=y
+CONFIG_BLK_DEV_IO_TRACE=y
+CONFIG_BLK_DEV_RAM_SIZE=16384
+CONFIG_BLK_DEV_RAM=y
+CONFIG_BLK_DEV_THROTTLING=y
+CONFIG_BONDING=y
+CONFIG_BOOTPARAM_HARDLOCKUP_PANIC=y
+CONFIG_BOOTTIME_TRACING=y
+CONFIG_BSD_DISKLABEL=y
+CONFIG_BSD_PROCESS_ACCT=y
+CONFIG_CFS_BANDWIDTH=y
+CONFIG_CGROUP_CPUACCT=y
+CONFIG_CGROUP_DEBUG=y
+CONFIG_CGROUP_DEVICE=y
+CONFIG_CGROUP_FREEZER=y
+CONFIG_CGROUP_HUGETLB=y
+CONFIG_CGROUP_NET_CLASSID=y
+CONFIG_CGROUP_NET_PRIO=y
+CONFIG_CGROUP_PERF=y
+CONFIG_CGROUP_PIDS=y
+CONFIG_CGROUP_RDMA=y
+CONFIG_CGROUP_SCHED=y
+CONFIG_CGROUPS=y
+CONFIG_CGROUP_WRITEBACK=y
+CONFIG_CMA_AREAS=7
+CONFIG_CMA=y
+CONFIG_COMPAT_32BIT_TIME=y
+CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE=y
+CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y
+CONFIG_CPU_FREQ_GOV_ONDEMAND=y
+CONFIG_CPU_FREQ_GOV_USERSPACE=y
+CONFIG_CPU_FREQ_STAT=y
+CONFIG_CPU_IDLE_GOV_LADDER=y
+CONFIG_CPUSETS=y
+CONFIG_CRC_T10DIF=y
+CONFIG_CRYPTO_BLAKE2B=y
+CONFIG_CRYPTO_DEV_VIRTIO=y
+CONFIG_CRYPTO_SEQIV=y
+CONFIG_CRYPTO_XXHASH=y
+CONFIG_DCB=y
+CONFIG_DEBUG_ATOMIC_SLEEP=y
+CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT=y
+CONFIG_DEBUG_MEMORY_INIT=y
+CONFIG_DEFAULT_FQ_CODEL=y
+CONFIG_DEFAULT_RENO=y
+CONFIG_DEFAULT_SECURITY_DAC=y
+CONFIG_DEVTMPFS_MOUNT=y
+CONFIG_DEVTMPFS=y
+CONFIG_DMA_CMA=y
+CONFIG_DNS_RESOLVER=y
+CONFIG_EFI_STUB=y
+CONFIG_EFI=y
+CONFIG_EXPERT=y
+CONFIG_EXT4_FS_POSIX_ACL=y
+CONFIG_EXT4_FS_SECURITY=y
+CONFIG_EXT4_FS=y
+CONFIG_FAIL_FUNCTION=y
+CONFIG_FAULT_INJECTION_DEBUG_FS=y
+CONFIG_FAULT_INJECTION=y
+CONFIG_FB_MODE_HELPERS=y
+CONFIG_FB_TILEBLITTING=y
+CONFIG_FB_VESA=y
+CONFIG_FB=y
+CONFIG_FONT_8x16=y
+CONFIG_FONT_MINI_4x6=y
+CONFIG_FONTS=y
+CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY=y
+CONFIG_FRAMEBUFFER_CONSOLE_ROTATION=y
+CONFIG_FRAMEBUFFER_CONSOLE=y
+CONFIG_FUSE_FS=y
+CONFIG_FW_LOADER_USER_HELPER=y
+CONFIG_GART_IOMMU=y
+CONFIG_GENERIC_PHY=y
+CONFIG_HARDLOCKUP_DETECTOR=y
+CONFIG_HIGH_RES_TIMERS=y
+CONFIG_HPET=y
+CONFIG_HUGETLBFS=y
+CONFIG_HUGETLB_PAGE=y
+CONFIG_HWPOISON_INJECT=y
+CONFIG_HZ_1000=y
+CONFIG_INET=y
+CONFIG_INTEL_POWERCLAMP=y
+CONFIG_IP6_NF_FILTER=y
+CONFIG_IP6_NF_IPTABLES=y
+CONFIG_IP6_NF_NAT=y
+CONFIG_IP6_NF_TARGET_MASQUERADE=y
+CONFIG_IP_ADVANCED_ROUTER=y
+CONFIG_IP_MROUTE=y
+CONFIG_IP_MULTICAST=y
+CONFIG_IP_MULTIPLE_TABLES=y
+CONFIG_IP_NF_FILTER=y
+CONFIG_IP_NF_IPTABLES=y
+CONFIG_IP_NF_NAT=y
+CONFIG_IP_NF_TARGET_MASQUERADE=y
+CONFIG_IP_PIMSM_V1=y
+CONFIG_IP_PIMSM_V2=y
+CONFIG_IP_ROUTE_MULTIPATH=y
+CONFIG_IP_ROUTE_VERBOSE=y
+CONFIG_IPV6_MIP6=y
+CONFIG_IPV6_ROUTE_INFO=y
+CONFIG_IPV6_ROUTER_PREF=y
+CONFIG_IPV6_SEG6_LWTUNNEL=y
+CONFIG_IPV6_SUBTREES=y
+CONFIG_IRQ_POLL=y
+CONFIG_JUMP_LABEL=y
+CONFIG_KARMA_PARTITION=y
+CONFIG_KEXEC=y
+CONFIG_KPROBES=y
+CONFIG_KSM=y
+CONFIG_LEGACY_VSYSCALL_NONE=y
+CONFIG_LOG_BUF_SHIFT=21
+CONFIG_LOG_CPU_MAX_BUF_SHIFT=0
+CONFIG_LOGO=y
+CONFIG_LSM="selinux,bpf,integrity"
+CONFIG_MAC_PARTITION=y
+CONFIG_MAGIC_SYSRQ=y
+CONFIG_MCORE2=y
+CONFIG_MEMCG=y
+CONFIG_MEMORY_FAILURE=y
+CONFIG_MINIX_SUBPARTITION=y
+CONFIG_MODULES=y
+CONFIG_NAMESPACES=y
+CONFIG_NET_9P_VIRTIO=y
+CONFIG_NET_9P=y
+CONFIG_NET_ACT_BPF=y
+CONFIG_NET_CLS_CGROUP=y
+CONFIG_NETDEVICES=y
+CONFIG_NET_EMATCH=y
+CONFIG_NETFILTER_NETLINK_LOG=y
+CONFIG_NETFILTER_NETLINK_QUEUE=y
+CONFIG_NETFILTER_XTABLES=y
+CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=y
+CONFIG_NETFILTER_XT_MATCH_BPF=y
+CONFIG_NETFILTER_XT_MATCH_COMMENT=y
+CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y
+CONFIG_NETFILTER_XT_MATCH_MARK=y
+CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y
+CONFIG_NETFILTER_XT_MATCH_STATISTIC=y
+CONFIG_NETFILTER_XT_NAT=y
+CONFIG_NETFILTER_XT_TARGET_MASQUERADE=y
+CONFIG_NET_IPGRE_BROADCAST=y
+CONFIG_NET_L3_MASTER_DEV=y
+CONFIG_NETLABEL=y
+CONFIG_NET_SCH_DEFAULT=y
+CONFIG_NET_SCHED=y
+CONFIG_NET_SCH_FQ_CODEL=y
+CONFIG_NET_TC_SKB_EXT=y
+CONFIG_NET_VRF=y
+CONFIG_NET=y
+CONFIG_NF_CONNTRACK=y
+CONFIG_NF_NAT_MASQUERADE=y
+CONFIG_NF_NAT=y
+CONFIG_NLS_ASCII=y
+CONFIG_NLS_CODEPAGE_437=y
+CONFIG_NLS_DEFAULT="utf8"
+CONFIG_NO_HZ=y
+CONFIG_NR_CPUS=128
+CONFIG_NUMA_BALANCING=y
+CONFIG_NUMA=y
+CONFIG_NVMEM=y
+CONFIG_OSF_PARTITION=y
+CONFIG_OVERLAY_FS_INDEX=y
+CONFIG_OVERLAY_FS_METACOPY=y
+CONFIG_OVERLAY_FS_XINO_AUTO=y
+CONFIG_OVERLAY_FS=y
+CONFIG_PACKET=y
+CONFIG_PANIC_ON_OOPS=y
+CONFIG_PARTITION_ADVANCED=y
+CONFIG_PCIEPORTBUS=y
+CONFIG_PCI_IOV=y
+CONFIG_PCI_MSI=y
+CONFIG_PCI=y
+CONFIG_PHYSICAL_ALIGN=0x1000000
+CONFIG_POSIX_MQUEUE=y
+CONFIG_POWER_SUPPLY=y
+CONFIG_PREEMPT=y
+CONFIG_PRINTK_TIME=y
+CONFIG_PROC_KCORE=y
+CONFIG_PROFILING=y
+CONFIG_PROVE_LOCKING=y
+CONFIG_PTP_1588_CLOCK=y
+CONFIG_RC_DEVICES=y
+CONFIG_RC_LOOPBACK=y
+CONFIG_RCU_CPU_STALL_TIMEOUT=60
+CONFIG_SCHED_STACK_END_CHECK=y
+CONFIG_SCHEDSTATS=y
+CONFIG_SECURITY_NETWORK=y
+CONFIG_SECURITY_SELINUX=y
+CONFIG_SERIAL_8250_CONSOLE=y
+CONFIG_SERIAL_8250_DETECT_IRQ=y
+CONFIG_SERIAL_8250_EXTENDED=y
+CONFIG_SERIAL_8250_MANY_PORTS=y
+CONFIG_SERIAL_8250_NR_UARTS=32
+CONFIG_SERIAL_8250_RSA=y
+CONFIG_SERIAL_8250_SHARE_IRQ=y
+CONFIG_SERIAL_8250=y
+CONFIG_SERIAL_NONSTANDARD=y
+CONFIG_SERIO_LIBPS2=y
+CONFIG_SGI_PARTITION=y
+CONFIG_SMP=y
+CONFIG_SOCK_CGROUP_DATA=y
+CONFIG_SOLARIS_X86_PARTITION=y
+CONFIG_SUN_PARTITION=y
+CONFIG_SYNC_FILE=y
+CONFIG_SYSVIPC=y
+CONFIG_TASK_DELAY_ACCT=y
+CONFIG_TASK_IO_ACCOUNTING=y
+CONFIG_TASKSTATS=y
+CONFIG_TASK_XACCT=y
+CONFIG_TCP_CONG_ADVANCED=y
+CONFIG_TCP_MD5SIG=y
+CONFIG_TLS=y
+CONFIG_TMPFS_POSIX_ACL=y
+CONFIG_TMPFS=y
+CONFIG_TRANSPARENT_HUGEPAGE_MADVISE=y
+CONFIG_TRANSPARENT_HUGEPAGE=y
+CONFIG_TUN=y
+CONFIG_UNIXWARE_DISKLABEL=y
+CONFIG_UNIX=y
+CONFIG_USER_NS=y
+CONFIG_VALIDATE_FS_PARSER=y
+CONFIG_VETH=y
+CONFIG_VIRT_DRIVERS=y
+CONFIG_VIRTIO_BALLOON=y
+CONFIG_VIRTIO_BLK=y
+CONFIG_VIRTIO_CONSOLE=y
+CONFIG_VIRTIO_FS=y
+CONFIG_VIRTIO_NET=y
+CONFIG_VIRTIO_PCI=y
+CONFIG_VLAN_8021Q=y
+CONFIG_XFRM_SUB_POLICY=y
+CONFIG_XFRM_USER=y
+CONFIG_ZEROPLUS_FF=y
+CONFIG_KASAN=y
diff --git a/tools/testing/selftests/hid/config.x86_64 b/tools/testing/selftests/hid/config.x86_64
new file mode 100644
index 000000000000..a8721f403c21
--- /dev/null
+++ b/tools/testing/selftests/hid/config.x86_64
@@ -0,0 +1,4 @@
+CONFIG_X86_ACPI_CPUFREQ=y
+CONFIG_X86_CPUID=y
+CONFIG_X86_MSR=y
+CONFIG_X86_POWERNOW_K8=y
diff --git a/tools/testing/selftests/hid/hid-apple.sh b/tools/testing/selftests/hid/hid-apple.sh
new file mode 100755
index 000000000000..656f2d5ae5a9
--- /dev/null
+++ b/tools/testing/selftests/hid/hid-apple.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Runs tests for the HID subsystem
+
+export TARGET=test_apple_keyboard.py
+
+bash ./run-hid-tools-tests.sh
diff --git a/tools/testing/selftests/hid/hid-core.sh b/tools/testing/selftests/hid/hid-core.sh
new file mode 100755
index 000000000000..5bbabc12c34f
--- /dev/null
+++ b/tools/testing/selftests/hid/hid-core.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Runs tests for the HID subsystem
+
+export TARGET=test_hid_core.py
+
+bash ./run-hid-tools-tests.sh
diff --git a/tools/testing/selftests/hid/hid-gamepad.sh b/tools/testing/selftests/hid/hid-gamepad.sh
new file mode 100755
index 000000000000..1ba00c0ca95f
--- /dev/null
+++ b/tools/testing/selftests/hid/hid-gamepad.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Runs tests for the HID subsystem
+
+export TARGET=test_gamepad.py
+
+bash ./run-hid-tools-tests.sh
diff --git a/tools/testing/selftests/hid/hid-ite.sh b/tools/testing/selftests/hid/hid-ite.sh
new file mode 100755
index 000000000000..52c5ccf42292
--- /dev/null
+++ b/tools/testing/selftests/hid/hid-ite.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Runs tests for the HID subsystem
+
+export TARGET=test_ite_keyboard.py
+
+bash ./run-hid-tools-tests.sh
diff --git a/tools/testing/selftests/hid/hid-keyboard.sh b/tools/testing/selftests/hid/hid-keyboard.sh
new file mode 100755
index 000000000000..55368f17d1d5
--- /dev/null
+++ b/tools/testing/selftests/hid/hid-keyboard.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Runs tests for the HID subsystem
+
+export TARGET=test_keyboard.py
+
+bash ./run-hid-tools-tests.sh
diff --git a/tools/testing/selftests/hid/hid-mouse.sh b/tools/testing/selftests/hid/hid-mouse.sh
new file mode 100755
index 000000000000..7b4ad4f646f7
--- /dev/null
+++ b/tools/testing/selftests/hid/hid-mouse.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Runs tests for the HID subsystem
+
+export TARGET=test_mouse.py
+
+bash ./run-hid-tools-tests.sh
diff --git a/tools/testing/selftests/hid/hid-multitouch.sh b/tools/testing/selftests/hid/hid-multitouch.sh
new file mode 100755
index 000000000000..d03a1ddbfb1f
--- /dev/null
+++ b/tools/testing/selftests/hid/hid-multitouch.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Runs tests for the HID subsystem
+
+export TARGET=test_multitouch.py
+
+bash ./run-hid-tools-tests.sh
diff --git a/tools/testing/selftests/hid/hid-sony.sh b/tools/testing/selftests/hid/hid-sony.sh
new file mode 100755
index 000000000000..c863c442686e
--- /dev/null
+++ b/tools/testing/selftests/hid/hid-sony.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Runs tests for the HID subsystem
+
+export TARGET=test_sony.py
+
+bash ./run-hid-tools-tests.sh
diff --git a/tools/testing/selftests/hid/hid-tablet.sh b/tools/testing/selftests/hid/hid-tablet.sh
new file mode 100755
index 000000000000..e86b3fedafd9
--- /dev/null
+++ b/tools/testing/selftests/hid/hid-tablet.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Runs tests for the HID subsystem
+
+export TARGET=test_tablet.py
+
+bash ./run-hid-tools-tests.sh
diff --git a/tools/testing/selftests/hid/hid-usb_crash.sh b/tools/testing/selftests/hid/hid-usb_crash.sh
new file mode 100755
index 000000000000..3f0debe7e8fd
--- /dev/null
+++ b/tools/testing/selftests/hid/hid-usb_crash.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Runs tests for the HID subsystem
+
+export TARGET=test_usb_crash.py
+
+bash ./run-hid-tools-tests.sh
diff --git a/tools/testing/selftests/hid/hid-wacom.sh b/tools/testing/selftests/hid/hid-wacom.sh
new file mode 100755
index 000000000000..1630c22726d2
--- /dev/null
+++ b/tools/testing/selftests/hid/hid-wacom.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Runs tests for the HID subsystem
+
+export TARGET=test_wacom_generic.py
+
+bash ./run-hid-tools-tests.sh
diff --git a/tools/testing/selftests/hid/hid_bpf.c b/tools/testing/selftests/hid/hid_bpf.c
new file mode 100644
index 000000000000..f825623e3edc
--- /dev/null
+++ b/tools/testing/selftests/hid/hid_bpf.c
@@ -0,0 +1,977 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2022 Red Hat */
+#include "hid.skel.h"
+
+#include "../kselftest_harness.h"
+
+#include <bpf/bpf.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <dirent.h>
+#include <poll.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <linux/hidraw.h>
+#include <linux/uhid.h>
+
+#define SHOW_UHID_DEBUG 0
+
+#define min(a, b) \
+ ({ __typeof__(a) _a = (a); \
+ __typeof__(b) _b = (b); \
+ _a < _b ? _a : _b; })
+
+static unsigned char rdesc[] = {
+ 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */
+ 0x09, 0x21, /* Usage (Vendor Usage 0x21) */
+ 0xa1, 0x01, /* COLLECTION (Application) */
+ 0x09, 0x01, /* Usage (Vendor Usage 0x01) */
+ 0xa1, 0x00, /* COLLECTION (Physical) */
+ 0x85, 0x02, /* REPORT_ID (2) */
+ 0x19, 0x01, /* USAGE_MINIMUM (1) */
+ 0x29, 0x08, /* USAGE_MAXIMUM (3) */
+ 0x15, 0x00, /* LOGICAL_MINIMUM (0) */
+ 0x25, 0xff, /* LOGICAL_MAXIMUM (255) */
+ 0x95, 0x08, /* REPORT_COUNT (8) */
+ 0x75, 0x08, /* REPORT_SIZE (8) */
+ 0x81, 0x02, /* INPUT (Data,Var,Abs) */
+ 0xc0, /* END_COLLECTION */
+ 0x09, 0x01, /* Usage (Vendor Usage 0x01) */
+ 0xa1, 0x00, /* COLLECTION (Physical) */
+ 0x85, 0x01, /* REPORT_ID (1) */
+ 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */
+ 0x19, 0x01, /* USAGE_MINIMUM (1) */
+ 0x29, 0x03, /* USAGE_MAXIMUM (3) */
+ 0x15, 0x00, /* LOGICAL_MINIMUM (0) */
+ 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */
+ 0x95, 0x03, /* REPORT_COUNT (3) */
+ 0x75, 0x01, /* REPORT_SIZE (1) */
+ 0x81, 0x02, /* INPUT (Data,Var,Abs) */
+ 0x95, 0x01, /* REPORT_COUNT (1) */
+ 0x75, 0x05, /* REPORT_SIZE (5) */
+ 0x81, 0x01, /* INPUT (Cnst,Var,Abs) */
+ 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */
+ 0x09, 0x30, /* USAGE (X) */
+ 0x09, 0x31, /* USAGE (Y) */
+ 0x15, 0x81, /* LOGICAL_MINIMUM (-127) */
+ 0x25, 0x7f, /* LOGICAL_MAXIMUM (127) */
+ 0x75, 0x10, /* REPORT_SIZE (16) */
+ 0x95, 0x02, /* REPORT_COUNT (2) */
+ 0x81, 0x06, /* INPUT (Data,Var,Rel) */
+
+ 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */
+ 0x19, 0x01, /* USAGE_MINIMUM (1) */
+ 0x29, 0x03, /* USAGE_MAXIMUM (3) */
+ 0x15, 0x00, /* LOGICAL_MINIMUM (0) */
+ 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */
+ 0x95, 0x03, /* REPORT_COUNT (3) */
+ 0x75, 0x01, /* REPORT_SIZE (1) */
+ 0x91, 0x02, /* Output (Data,Var,Abs) */
+ 0x95, 0x01, /* REPORT_COUNT (1) */
+ 0x75, 0x05, /* REPORT_SIZE (5) */
+ 0x91, 0x01, /* Output (Cnst,Var,Abs) */
+
+ 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */
+ 0x19, 0x06, /* USAGE_MINIMUM (6) */
+ 0x29, 0x08, /* USAGE_MAXIMUM (8) */
+ 0x15, 0x00, /* LOGICAL_MINIMUM (0) */
+ 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */
+ 0x95, 0x03, /* REPORT_COUNT (3) */
+ 0x75, 0x01, /* REPORT_SIZE (1) */
+ 0xb1, 0x02, /* Feature (Data,Var,Abs) */
+ 0x95, 0x01, /* REPORT_COUNT (1) */
+ 0x75, 0x05, /* REPORT_SIZE (5) */
+ 0x91, 0x01, /* Output (Cnst,Var,Abs) */
+
+ 0xc0, /* END_COLLECTION */
+ 0xc0, /* END_COLLECTION */
+};
+
+static __u8 feature_data[] = { 1, 2 };
+
+struct attach_prog_args {
+ int prog_fd;
+ unsigned int hid;
+ int retval;
+ int insert_head;
+};
+
+struct hid_hw_request_syscall_args {
+ __u8 data[10];
+ unsigned int hid;
+ int retval;
+ size_t size;
+ enum hid_report_type type;
+ __u8 request_type;
+};
+
+#define ASSERT_OK(data) ASSERT_FALSE(data)
+#define ASSERT_OK_PTR(ptr) ASSERT_NE(NULL, ptr)
+
+#define UHID_LOG(fmt, ...) do { \
+ if (SHOW_UHID_DEBUG) \
+ TH_LOG(fmt, ##__VA_ARGS__); \
+} while (0)
+
+static pthread_mutex_t uhid_started_mtx = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t uhid_started = PTHREAD_COND_INITIALIZER;
+
+static pthread_mutex_t uhid_output_mtx = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t uhid_output_cond = PTHREAD_COND_INITIALIZER;
+static unsigned char output_report[10];
+
+/* no need to protect uhid_stopped, only one thread accesses it */
+static bool uhid_stopped;
+
+static int uhid_write(struct __test_metadata *_metadata, int fd, const struct uhid_event *ev)
+{
+ ssize_t ret;
+
+ ret = write(fd, ev, sizeof(*ev));
+ if (ret < 0) {
+ TH_LOG("Cannot write to uhid: %m");
+ return -errno;
+ } else if (ret != sizeof(*ev)) {
+ TH_LOG("Wrong size written to uhid: %zd != %zu",
+ ret, sizeof(ev));
+ return -EFAULT;
+ } else {
+ return 0;
+ }
+}
+
+static int uhid_create(struct __test_metadata *_metadata, int fd, int rand_nb)
+{
+ struct uhid_event ev;
+ char buf[25];
+
+ sprintf(buf, "test-uhid-device-%d", rand_nb);
+
+ memset(&ev, 0, sizeof(ev));
+ ev.type = UHID_CREATE;
+ strcpy((char *)ev.u.create.name, buf);
+ ev.u.create.rd_data = rdesc;
+ ev.u.create.rd_size = sizeof(rdesc);
+ ev.u.create.bus = BUS_USB;
+ ev.u.create.vendor = 0x0001;
+ ev.u.create.product = 0x0a37;
+ ev.u.create.version = 0;
+ ev.u.create.country = 0;
+
+ sprintf(buf, "%d", rand_nb);
+ strcpy((char *)ev.u.create.phys, buf);
+
+ return uhid_write(_metadata, fd, &ev);
+}
+
+static void uhid_destroy(struct __test_metadata *_metadata, int fd)
+{
+ struct uhid_event ev;
+
+ memset(&ev, 0, sizeof(ev));
+ ev.type = UHID_DESTROY;
+
+ uhid_write(_metadata, fd, &ev);
+}
+
+static int uhid_event(struct __test_metadata *_metadata, int fd)
+{
+ struct uhid_event ev, answer;
+ ssize_t ret;
+
+ memset(&ev, 0, sizeof(ev));
+ ret = read(fd, &ev, sizeof(ev));
+ if (ret == 0) {
+ UHID_LOG("Read HUP on uhid-cdev");
+ return -EFAULT;
+ } else if (ret < 0) {
+ UHID_LOG("Cannot read uhid-cdev: %m");
+ return -errno;
+ } else if (ret != sizeof(ev)) {
+ UHID_LOG("Invalid size read from uhid-dev: %zd != %zu",
+ ret, sizeof(ev));
+ return -EFAULT;
+ }
+
+ switch (ev.type) {
+ case UHID_START:
+ pthread_mutex_lock(&uhid_started_mtx);
+ pthread_cond_signal(&uhid_started);
+ pthread_mutex_unlock(&uhid_started_mtx);
+
+ UHID_LOG("UHID_START from uhid-dev");
+ break;
+ case UHID_STOP:
+ uhid_stopped = true;
+
+ UHID_LOG("UHID_STOP from uhid-dev");
+ break;
+ case UHID_OPEN:
+ UHID_LOG("UHID_OPEN from uhid-dev");
+ break;
+ case UHID_CLOSE:
+ UHID_LOG("UHID_CLOSE from uhid-dev");
+ break;
+ case UHID_OUTPUT:
+ UHID_LOG("UHID_OUTPUT from uhid-dev");
+
+ pthread_mutex_lock(&uhid_output_mtx);
+ memcpy(output_report,
+ ev.u.output.data,
+ min(ev.u.output.size, sizeof(output_report)));
+ pthread_cond_signal(&uhid_output_cond);
+ pthread_mutex_unlock(&uhid_output_mtx);
+ break;
+ case UHID_GET_REPORT:
+ UHID_LOG("UHID_GET_REPORT from uhid-dev");
+
+ answer.type = UHID_GET_REPORT_REPLY;
+ answer.u.get_report_reply.id = ev.u.get_report.id;
+ answer.u.get_report_reply.err = ev.u.get_report.rnum == 1 ? 0 : -EIO;
+ answer.u.get_report_reply.size = sizeof(feature_data);
+ memcpy(answer.u.get_report_reply.data, feature_data, sizeof(feature_data));
+
+ uhid_write(_metadata, fd, &answer);
+
+ break;
+ case UHID_SET_REPORT:
+ UHID_LOG("UHID_SET_REPORT from uhid-dev");
+ break;
+ default:
+ TH_LOG("Invalid event from uhid-dev: %u", ev.type);
+ }
+
+ return 0;
+}
+
+struct uhid_thread_args {
+ int fd;
+ struct __test_metadata *_metadata;
+};
+static void *uhid_read_events_thread(void *arg)
+{
+ struct uhid_thread_args *args = (struct uhid_thread_args *)arg;
+ struct __test_metadata *_metadata = args->_metadata;
+ struct pollfd pfds[1];
+ int fd = args->fd;
+ int ret = 0;
+
+ pfds[0].fd = fd;
+ pfds[0].events = POLLIN;
+
+ uhid_stopped = false;
+
+ while (!uhid_stopped) {
+ ret = poll(pfds, 1, 100);
+ if (ret < 0) {
+ TH_LOG("Cannot poll for fds: %m");
+ break;
+ }
+ if (pfds[0].revents & POLLIN) {
+ ret = uhid_event(_metadata, fd);
+ if (ret)
+ break;
+ }
+ }
+
+ return (void *)(long)ret;
+}
+
+static int uhid_start_listener(struct __test_metadata *_metadata, pthread_t *tid, int uhid_fd)
+{
+ struct uhid_thread_args args = {
+ .fd = uhid_fd,
+ ._metadata = _metadata,
+ };
+ int err;
+
+ pthread_mutex_lock(&uhid_started_mtx);
+ err = pthread_create(tid, NULL, uhid_read_events_thread, (void *)&args);
+ ASSERT_EQ(0, err) {
+ TH_LOG("Could not start the uhid thread: %d", err);
+ pthread_mutex_unlock(&uhid_started_mtx);
+ close(uhid_fd);
+ return -EIO;
+ }
+ pthread_cond_wait(&uhid_started, &uhid_started_mtx);
+ pthread_mutex_unlock(&uhid_started_mtx);
+
+ return 0;
+}
+
+static int uhid_send_event(struct __test_metadata *_metadata, int fd, __u8 *buf, size_t size)
+{
+ struct uhid_event ev;
+
+ if (size > sizeof(ev.u.input.data))
+ return -E2BIG;
+
+ memset(&ev, 0, sizeof(ev));
+ ev.type = UHID_INPUT2;
+ ev.u.input2.size = size;
+
+ memcpy(ev.u.input2.data, buf, size);
+
+ return uhid_write(_metadata, fd, &ev);
+}
+
+static int setup_uhid(struct __test_metadata *_metadata, int rand_nb)
+{
+ int fd;
+ const char *path = "/dev/uhid";
+ int ret;
+
+ fd = open(path, O_RDWR | O_CLOEXEC);
+ ASSERT_GE(fd, 0) TH_LOG("open uhid-cdev failed; %d", fd);
+
+ ret = uhid_create(_metadata, fd, rand_nb);
+ ASSERT_EQ(0, ret) {
+ TH_LOG("create uhid device failed: %d", ret);
+ close(fd);
+ }
+
+ return fd;
+}
+
+static bool match_sysfs_device(int dev_id, const char *workdir, struct dirent *dir)
+{
+ const char *target = "0003:0001:0A37.*";
+ char phys[512];
+ char uevent[1024];
+ char temp[512];
+ int fd, nread;
+ bool found = false;
+
+ if (fnmatch(target, dir->d_name, 0))
+ return false;
+
+ /* we found the correct VID/PID, now check for phys */
+ sprintf(uevent, "%s/%s/uevent", workdir, dir->d_name);
+
+ fd = open(uevent, O_RDONLY | O_NONBLOCK);
+ if (fd < 0)
+ return false;
+
+ sprintf(phys, "PHYS=%d", dev_id);
+
+ nread = read(fd, temp, ARRAY_SIZE(temp));
+ if (nread > 0 && (strstr(temp, phys)) != NULL)
+ found = true;
+
+ close(fd);
+
+ return found;
+}
+
+static int get_hid_id(int dev_id)
+{
+ const char *workdir = "/sys/devices/virtual/misc/uhid";
+ const char *str_id;
+ DIR *d;
+ struct dirent *dir;
+ int found = -1, attempts = 3;
+
+ /* it would be nice to be able to use nftw, but the no_alu32 target doesn't support it */
+
+ while (found < 0 && attempts > 0) {
+ attempts--;
+ d = opendir(workdir);
+ if (d) {
+ while ((dir = readdir(d)) != NULL) {
+ if (!match_sysfs_device(dev_id, workdir, dir))
+ continue;
+
+ str_id = dir->d_name + sizeof("0003:0001:0A37.");
+ found = (int)strtol(str_id, NULL, 16);
+
+ break;
+ }
+ closedir(d);
+ }
+ if (found < 0)
+ usleep(100000);
+ }
+
+ return found;
+}
+
+static int get_hidraw(int dev_id)
+{
+ const char *workdir = "/sys/devices/virtual/misc/uhid";
+ char sysfs[1024];
+ DIR *d, *subd;
+ struct dirent *dir, *subdir;
+ int i, found = -1;
+
+ /* retry 5 times in case the system is loaded */
+ for (i = 5; i > 0; i--) {
+ usleep(10);
+ d = opendir(workdir);
+
+ if (!d)
+ continue;
+
+ while ((dir = readdir(d)) != NULL) {
+ if (!match_sysfs_device(dev_id, workdir, dir))
+ continue;
+
+ sprintf(sysfs, "%s/%s/hidraw", workdir, dir->d_name);
+
+ subd = opendir(sysfs);
+ if (!subd)
+ continue;
+
+ while ((subdir = readdir(subd)) != NULL) {
+ if (fnmatch("hidraw*", subdir->d_name, 0))
+ continue;
+
+ found = atoi(subdir->d_name + strlen("hidraw"));
+ }
+
+ closedir(subd);
+
+ if (found > 0)
+ break;
+ }
+ closedir(d);
+ }
+
+ return found;
+}
+
+static int open_hidraw(int dev_id)
+{
+ int hidraw_number;
+ char hidraw_path[64] = { 0 };
+
+ hidraw_number = get_hidraw(dev_id);
+ if (hidraw_number < 0)
+ return hidraw_number;
+
+ /* open hidraw node to check the other side of the pipe */
+ sprintf(hidraw_path, "/dev/hidraw%d", hidraw_number);
+ return open(hidraw_path, O_RDWR | O_NONBLOCK);
+}
+
+FIXTURE(hid_bpf) {
+ int dev_id;
+ int uhid_fd;
+ int hidraw_fd;
+ int hid_id;
+ pthread_t tid;
+ struct hid *skel;
+ int hid_links[3]; /* max number of programs loaded in a single test */
+};
+static void detach_bpf(FIXTURE_DATA(hid_bpf) * self)
+{
+ int i;
+
+ if (self->hidraw_fd)
+ close(self->hidraw_fd);
+ self->hidraw_fd = 0;
+
+ for (i = 0; i < ARRAY_SIZE(self->hid_links); i++) {
+ if (self->hid_links[i])
+ close(self->hid_links[i]);
+ }
+
+ hid__destroy(self->skel);
+ self->skel = NULL;
+}
+
+FIXTURE_TEARDOWN(hid_bpf) {
+ void *uhid_err;
+
+ uhid_destroy(_metadata, self->uhid_fd);
+
+ detach_bpf(self);
+ pthread_join(self->tid, &uhid_err);
+}
+#define TEARDOWN_LOG(fmt, ...) do { \
+ TH_LOG(fmt, ##__VA_ARGS__); \
+ hid_bpf_teardown(_metadata, self, variant); \
+} while (0)
+
+FIXTURE_SETUP(hid_bpf)
+{
+ time_t t;
+ int err;
+
+ /* initialize random number generator */
+ srand((unsigned int)time(&t));
+
+ self->dev_id = rand() % 1024;
+
+ self->uhid_fd = setup_uhid(_metadata, self->dev_id);
+
+ /* locate the uev, self, variant);ent file of the created device */
+ self->hid_id = get_hid_id(self->dev_id);
+ ASSERT_GT(self->hid_id, 0)
+ TEARDOWN_LOG("Could not locate uhid device id: %d", self->hid_id);
+
+ err = uhid_start_listener(_metadata, &self->tid, self->uhid_fd);
+ ASSERT_EQ(0, err) TEARDOWN_LOG("could not start udev listener: %d", err);
+}
+
+struct test_program {
+ const char *name;
+ int insert_head;
+};
+#define LOAD_PROGRAMS(progs) \
+ load_programs(progs, ARRAY_SIZE(progs), _metadata, self, variant)
+#define LOAD_BPF \
+ load_programs(NULL, 0, _metadata, self, variant)
+static void load_programs(const struct test_program programs[],
+ const size_t progs_count,
+ struct __test_metadata *_metadata,
+ FIXTURE_DATA(hid_bpf) * self,
+ const FIXTURE_VARIANT(hid_bpf) * variant)
+{
+ int attach_fd, err = -EINVAL;
+ struct attach_prog_args args = {
+ .retval = -1,
+ };
+ DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattr,
+ .ctx_in = &args,
+ .ctx_size_in = sizeof(args),
+ );
+
+ ASSERT_LE(progs_count, ARRAY_SIZE(self->hid_links))
+ TH_LOG("too many programs are to be loaded");
+
+ /* open the bpf file */
+ self->skel = hid__open();
+ ASSERT_OK_PTR(self->skel) TEARDOWN_LOG("Error while calling hid__open");
+
+ for (int i = 0; i < progs_count; i++) {
+ struct bpf_program *prog;
+
+ prog = bpf_object__find_program_by_name(*self->skel->skeleton->obj,
+ programs[i].name);
+ ASSERT_OK_PTR(prog) TH_LOG("can not find program by name '%s'", programs[i].name);
+
+ bpf_program__set_autoload(prog, true);
+ }
+
+ err = hid__load(self->skel);
+ ASSERT_OK(err) TH_LOG("hid_skel_load failed: %d", err);
+
+ attach_fd = bpf_program__fd(self->skel->progs.attach_prog);
+ ASSERT_GE(attach_fd, 0) TH_LOG("locate attach_prog: %d", attach_fd);
+
+ for (int i = 0; i < progs_count; i++) {
+ struct bpf_program *prog;
+
+ prog = bpf_object__find_program_by_name(*self->skel->skeleton->obj,
+ programs[i].name);
+ ASSERT_OK_PTR(prog) TH_LOG("can not find program by name '%s'", programs[i].name);
+
+ args.prog_fd = bpf_program__fd(prog);
+ args.hid = self->hid_id;
+ args.insert_head = programs[i].insert_head;
+ err = bpf_prog_test_run_opts(attach_fd, &tattr);
+ ASSERT_GE(args.retval, 0)
+ TH_LOG("attach_hid(%s): %d", programs[i].name, args.retval);
+
+ self->hid_links[i] = args.retval;
+ }
+
+ self->hidraw_fd = open_hidraw(self->dev_id);
+ ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw");
+}
+
+/*
+ * A simple test to see if the fixture is working fine.
+ * If this fails, none of the other tests will pass.
+ */
+TEST_F(hid_bpf, test_create_uhid)
+{
+}
+
+/*
+ * Attach hid_first_event to the given uhid device,
+ * retrieve and open the matching hidraw node,
+ * inject one event in the uhid device,
+ * check that the program sees it and can change the data
+ */
+TEST_F(hid_bpf, raw_event)
+{
+ const struct test_program progs[] = {
+ { .name = "hid_first_event" },
+ };
+ __u8 buf[10] = {0};
+ int err;
+
+ LOAD_PROGRAMS(progs);
+
+ /* check that the program is correctly loaded */
+ ASSERT_EQ(self->skel->data->callback_check, 52) TH_LOG("callback_check1");
+ ASSERT_EQ(self->skel->data->callback2_check, 52) TH_LOG("callback2_check1");
+
+ /* inject one event */
+ buf[0] = 1;
+ buf[1] = 42;
+ uhid_send_event(_metadata, self->uhid_fd, buf, 6);
+
+ /* check that hid_first_event() was executed */
+ ASSERT_EQ(self->skel->data->callback_check, 42) TH_LOG("callback_check1");
+
+ /* read the data from hidraw */
+ memset(buf, 0, sizeof(buf));
+ err = read(self->hidraw_fd, buf, sizeof(buf));
+ ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
+ ASSERT_EQ(buf[0], 1);
+ ASSERT_EQ(buf[2], 47);
+
+ /* inject another event */
+ memset(buf, 0, sizeof(buf));
+ buf[0] = 1;
+ buf[1] = 47;
+ uhid_send_event(_metadata, self->uhid_fd, buf, 6);
+
+ /* check that hid_first_event() was executed */
+ ASSERT_EQ(self->skel->data->callback_check, 47) TH_LOG("callback_check1");
+
+ /* read the data from hidraw */
+ memset(buf, 0, sizeof(buf));
+ err = read(self->hidraw_fd, buf, sizeof(buf));
+ ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
+ ASSERT_EQ(buf[2], 52);
+}
+
+/*
+ * Ensures that we can attach/detach programs
+ */
+TEST_F(hid_bpf, test_attach_detach)
+{
+ const struct test_program progs[] = {
+ { .name = "hid_first_event" },
+ { .name = "hid_second_event" },
+ };
+ __u8 buf[10] = {0};
+ int err, link;
+
+ LOAD_PROGRAMS(progs);
+
+ link = self->hid_links[0];
+ ASSERT_GT(link, 0) TH_LOG("HID-BPF link not created");
+
+ /* inject one event */
+ buf[0] = 1;
+ buf[1] = 42;
+ uhid_send_event(_metadata, self->uhid_fd, buf, 6);
+
+ /* read the data from hidraw */
+ memset(buf, 0, sizeof(buf));
+ err = read(self->hidraw_fd, buf, sizeof(buf));
+ ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
+ ASSERT_EQ(buf[0], 1);
+ ASSERT_EQ(buf[2], 47);
+
+ /* make sure both programs are run */
+ ASSERT_EQ(buf[3], 52);
+
+ /* pin the first program and immediately unpin it */
+#define PIN_PATH "/sys/fs/bpf/hid_first_event"
+ err = bpf_obj_pin(link, PIN_PATH);
+ ASSERT_OK(err) TH_LOG("error while calling bpf_obj_pin");
+ remove(PIN_PATH);
+#undef PIN_PATH
+ usleep(100000);
+
+ /* detach the program */
+ detach_bpf(self);
+
+ self->hidraw_fd = open_hidraw(self->dev_id);
+ ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw");
+
+ /* inject another event */
+ memset(buf, 0, sizeof(buf));
+ buf[0] = 1;
+ buf[1] = 47;
+ uhid_send_event(_metadata, self->uhid_fd, buf, 6);
+
+ /* read the data from hidraw */
+ memset(buf, 0, sizeof(buf));
+ err = read(self->hidraw_fd, buf, sizeof(buf));
+ ASSERT_EQ(err, 6) TH_LOG("read_hidraw_no_bpf");
+ ASSERT_EQ(buf[0], 1);
+ ASSERT_EQ(buf[1], 47);
+ ASSERT_EQ(buf[2], 0);
+ ASSERT_EQ(buf[3], 0);
+
+ /* re-attach our program */
+
+ LOAD_PROGRAMS(progs);
+
+ /* inject one event */
+ memset(buf, 0, sizeof(buf));
+ buf[0] = 1;
+ buf[1] = 42;
+ uhid_send_event(_metadata, self->uhid_fd, buf, 6);
+
+ /* read the data from hidraw */
+ memset(buf, 0, sizeof(buf));
+ err = read(self->hidraw_fd, buf, sizeof(buf));
+ ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
+ ASSERT_EQ(buf[0], 1);
+ ASSERT_EQ(buf[2], 47);
+ ASSERT_EQ(buf[3], 52);
+}
+
+/*
+ * Attach hid_change_report_id to the given uhid device,
+ * retrieve and open the matching hidraw node,
+ * inject one event in the uhid device,
+ * check that the program sees it and can change the data
+ */
+TEST_F(hid_bpf, test_hid_change_report)
+{
+ const struct test_program progs[] = {
+ { .name = "hid_change_report_id" },
+ };
+ __u8 buf[10] = {0};
+ int err;
+
+ LOAD_PROGRAMS(progs);
+
+ /* inject one event */
+ buf[0] = 1;
+ buf[1] = 42;
+ uhid_send_event(_metadata, self->uhid_fd, buf, 6);
+
+ /* read the data from hidraw */
+ memset(buf, 0, sizeof(buf));
+ err = read(self->hidraw_fd, buf, sizeof(buf));
+ ASSERT_EQ(err, 9) TH_LOG("read_hidraw");
+ ASSERT_EQ(buf[0], 2);
+ ASSERT_EQ(buf[1], 42);
+ ASSERT_EQ(buf[2], 0) TH_LOG("leftovers_from_previous_test");
+}
+
+/*
+ * Call hid_bpf_input_report against the given uhid device,
+ * check that the program is called and does the expected.
+ */
+TEST_F(hid_bpf, test_hid_user_input_report_call)
+{
+ struct hid_hw_request_syscall_args args = {
+ .retval = -1,
+ .size = 10,
+ };
+ DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs,
+ .ctx_in = &args,
+ .ctx_size_in = sizeof(args),
+ );
+ __u8 buf[10] = {0};
+ int err, prog_fd;
+
+ LOAD_BPF;
+
+ args.hid = self->hid_id;
+ args.data[0] = 1; /* report ID */
+ args.data[1] = 2; /* report ID */
+ args.data[2] = 42; /* report ID */
+
+ prog_fd = bpf_program__fd(self->skel->progs.hid_user_input_report);
+
+ /* check that there is no data to read from hidraw */
+ memset(buf, 0, sizeof(buf));
+ err = read(self->hidraw_fd, buf, sizeof(buf));
+ ASSERT_EQ(err, -1) TH_LOG("read_hidraw");
+
+ err = bpf_prog_test_run_opts(prog_fd, &tattrs);
+
+ ASSERT_OK(err) TH_LOG("error while calling bpf_prog_test_run_opts");
+
+ ASSERT_EQ(args.retval, 0);
+
+ /* read the data from hidraw */
+ memset(buf, 0, sizeof(buf));
+ err = read(self->hidraw_fd, buf, sizeof(buf));
+ ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
+ ASSERT_EQ(buf[0], 1);
+ ASSERT_EQ(buf[1], 2);
+ ASSERT_EQ(buf[2], 42);
+}
+
+/*
+ * Call hid_bpf_hw_output_report against the given uhid device,
+ * check that the program is called and does the expected.
+ */
+TEST_F(hid_bpf, test_hid_user_output_report_call)
+{
+ struct hid_hw_request_syscall_args args = {
+ .retval = -1,
+ .size = 10,
+ };
+ DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs,
+ .ctx_in = &args,
+ .ctx_size_in = sizeof(args),
+ );
+ int err, cond_err, prog_fd;
+ struct timespec time_to_wait;
+
+ LOAD_BPF;
+
+ args.hid = self->hid_id;
+ args.data[0] = 1; /* report ID */
+ args.data[1] = 2; /* report ID */
+ args.data[2] = 42; /* report ID */
+
+ prog_fd = bpf_program__fd(self->skel->progs.hid_user_output_report);
+
+ pthread_mutex_lock(&uhid_output_mtx);
+
+ memset(output_report, 0, sizeof(output_report));
+ clock_gettime(CLOCK_REALTIME, &time_to_wait);
+ time_to_wait.tv_sec += 2;
+
+ err = bpf_prog_test_run_opts(prog_fd, &tattrs);
+ cond_err = pthread_cond_timedwait(&uhid_output_cond, &uhid_output_mtx, &time_to_wait);
+
+ ASSERT_OK(err) TH_LOG("error while calling bpf_prog_test_run_opts");
+ ASSERT_OK(cond_err) TH_LOG("error while calling waiting for the condition");
+
+ ASSERT_EQ(args.retval, 3);
+
+ ASSERT_EQ(output_report[0], 1);
+ ASSERT_EQ(output_report[1], 2);
+ ASSERT_EQ(output_report[2], 42);
+
+ pthread_mutex_unlock(&uhid_output_mtx);
+}
+
+/*
+ * Call hid_hw_raw_request against the given uhid device,
+ * check that the program is called and does the expected.
+ */
+TEST_F(hid_bpf, test_hid_user_raw_request_call)
+{
+ struct hid_hw_request_syscall_args args = {
+ .retval = -1,
+ .type = HID_FEATURE_REPORT,
+ .request_type = HID_REQ_GET_REPORT,
+ .size = 10,
+ };
+ DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs,
+ .ctx_in = &args,
+ .ctx_size_in = sizeof(args),
+ );
+ int err, prog_fd;
+
+ LOAD_BPF;
+
+ args.hid = self->hid_id;
+ args.data[0] = 1; /* report ID */
+
+ prog_fd = bpf_program__fd(self->skel->progs.hid_user_raw_request);
+
+ err = bpf_prog_test_run_opts(prog_fd, &tattrs);
+ ASSERT_OK(err) TH_LOG("error while calling bpf_prog_test_run_opts");
+
+ ASSERT_EQ(args.retval, 2);
+
+ ASSERT_EQ(args.data[1], 2);
+}
+
+/*
+ * Attach hid_insert{0,1,2} to the given uhid device,
+ * retrieve and open the matching hidraw node,
+ * inject one event in the uhid device,
+ * check that the programs have been inserted in the correct order.
+ */
+TEST_F(hid_bpf, test_hid_attach_flags)
+{
+ const struct test_program progs[] = {
+ {
+ .name = "hid_test_insert2",
+ .insert_head = 0,
+ },
+ {
+ .name = "hid_test_insert1",
+ .insert_head = 1,
+ },
+ {
+ .name = "hid_test_insert3",
+ .insert_head = 0,
+ },
+ };
+ __u8 buf[10] = {0};
+ int err;
+
+ LOAD_PROGRAMS(progs);
+
+ /* inject one event */
+ buf[0] = 1;
+ uhid_send_event(_metadata, self->uhid_fd, buf, 6);
+
+ /* read the data from hidraw */
+ memset(buf, 0, sizeof(buf));
+ err = read(self->hidraw_fd, buf, sizeof(buf));
+ ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
+ ASSERT_EQ(buf[1], 1);
+ ASSERT_EQ(buf[2], 2);
+ ASSERT_EQ(buf[3], 3);
+}
+
+/*
+ * Attach hid_rdesc_fixup to the given uhid device,
+ * retrieve and open the matching hidraw node,
+ * check that the hidraw report descriptor has been updated.
+ */
+TEST_F(hid_bpf, test_rdesc_fixup)
+{
+ struct hidraw_report_descriptor rpt_desc = {0};
+ const struct test_program progs[] = {
+ { .name = "hid_rdesc_fixup" },
+ };
+ int err, desc_size;
+
+ LOAD_PROGRAMS(progs);
+
+ /* check that hid_rdesc_fixup() was executed */
+ ASSERT_EQ(self->skel->data->callback2_check, 0x21);
+
+ /* read the exposed report descriptor from hidraw */
+ err = ioctl(self->hidraw_fd, HIDIOCGRDESCSIZE, &desc_size);
+ ASSERT_GE(err, 0) TH_LOG("error while reading HIDIOCGRDESCSIZE: %d", err);
+
+ /* ensure the new size of the rdesc is bigger than the old one */
+ ASSERT_GT(desc_size, sizeof(rdesc));
+
+ rpt_desc.size = desc_size;
+ err = ioctl(self->hidraw_fd, HIDIOCGRDESC, &rpt_desc);
+ ASSERT_GE(err, 0) TH_LOG("error while reading HIDIOCGRDESC: %d", err);
+
+ ASSERT_EQ(rpt_desc.value[4], 0x42);
+}
+
+static int libbpf_print_fn(enum libbpf_print_level level,
+ const char *format, va_list args)
+{
+ char buf[1024];
+
+ if (level == LIBBPF_DEBUG)
+ return 0;
+
+ snprintf(buf, sizeof(buf), "# %s", format);
+
+ vfprintf(stdout, buf, args);
+ return 0;
+}
+
+static void __attribute__((constructor)) __constructor_order_last(void)
+{
+ if (!__constructor_order)
+ __constructor_order = _CONSTRUCTOR_ORDER_BACKWARD;
+}
+
+int main(int argc, char **argv)
+{
+ /* Use libbpf 1.0 API mode */
+ libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
+ libbpf_set_print(libbpf_print_fn);
+
+ return test_harness_run(argc, argv);
+}
diff --git a/tools/testing/selftests/hid/progs/hid.c b/tools/testing/selftests/hid/progs/hid.c
new file mode 100644
index 000000000000..f67d35def142
--- /dev/null
+++ b/tools/testing/selftests/hid/progs/hid.c
@@ -0,0 +1,252 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2022 Red hat */
+#include "hid_bpf_helpers.h"
+
+char _license[] SEC("license") = "GPL";
+
+struct attach_prog_args {
+ int prog_fd;
+ unsigned int hid;
+ int retval;
+ int insert_head;
+};
+
+__u64 callback_check = 52;
+__u64 callback2_check = 52;
+
+SEC("?fmod_ret/hid_bpf_device_event")
+int BPF_PROG(hid_first_event, struct hid_bpf_ctx *hid_ctx)
+{
+ __u8 *rw_data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 3 /* size */);
+
+ if (!rw_data)
+ return 0; /* EPERM check */
+
+ callback_check = rw_data[1];
+
+ rw_data[2] = rw_data[1] + 5;
+
+ return hid_ctx->size;
+}
+
+SEC("?fmod_ret/hid_bpf_device_event")
+int BPF_PROG(hid_second_event, struct hid_bpf_ctx *hid_ctx)
+{
+ __u8 *rw_data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 4 /* size */);
+
+ if (!rw_data)
+ return 0; /* EPERM check */
+
+ rw_data[3] = rw_data[2] + 5;
+
+ return hid_ctx->size;
+}
+
+SEC("?fmod_ret/hid_bpf_device_event")
+int BPF_PROG(hid_change_report_id, struct hid_bpf_ctx *hid_ctx)
+{
+ __u8 *rw_data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 3 /* size */);
+
+ if (!rw_data)
+ return 0; /* EPERM check */
+
+ rw_data[0] = 2;
+
+ return 9;
+}
+
+SEC("syscall")
+int attach_prog(struct attach_prog_args *ctx)
+{
+ ctx->retval = hid_bpf_attach_prog(ctx->hid,
+ ctx->prog_fd,
+ ctx->insert_head ? HID_BPF_FLAG_INSERT_HEAD :
+ HID_BPF_FLAG_NONE);
+ return 0;
+}
+
+struct hid_hw_request_syscall_args {
+ /* data needs to come at offset 0 so we can use it in calls */
+ __u8 data[10];
+ unsigned int hid;
+ int retval;
+ size_t size;
+ enum hid_report_type type;
+ __u8 request_type;
+};
+
+SEC("syscall")
+int hid_user_raw_request(struct hid_hw_request_syscall_args *args)
+{
+ struct hid_bpf_ctx *ctx;
+ const size_t size = args->size;
+ int i, ret = 0;
+
+ if (size > sizeof(args->data))
+ return -7; /* -E2BIG */
+
+ ctx = hid_bpf_allocate_context(args->hid);
+ if (!ctx)
+ return -1; /* EPERM check */
+
+ ret = hid_bpf_hw_request(ctx,
+ args->data,
+ size,
+ args->type,
+ args->request_type);
+ args->retval = ret;
+
+ hid_bpf_release_context(ctx);
+
+ return 0;
+}
+
+SEC("syscall")
+int hid_user_output_report(struct hid_hw_request_syscall_args *args)
+{
+ struct hid_bpf_ctx *ctx;
+ const size_t size = args->size;
+ int i, ret = 0;
+
+ if (size > sizeof(args->data))
+ return -7; /* -E2BIG */
+
+ ctx = hid_bpf_allocate_context(args->hid);
+ if (!ctx)
+ return -1; /* EPERM check */
+
+ ret = hid_bpf_hw_output_report(ctx,
+ args->data,
+ size);
+ args->retval = ret;
+
+ hid_bpf_release_context(ctx);
+
+ return 0;
+}
+
+SEC("syscall")
+int hid_user_input_report(struct hid_hw_request_syscall_args *args)
+{
+ struct hid_bpf_ctx *ctx;
+ const size_t size = args->size;
+ int i, ret = 0;
+
+ if (size > sizeof(args->data))
+ return -7; /* -E2BIG */
+
+ ctx = hid_bpf_allocate_context(args->hid);
+ if (!ctx)
+ return -1; /* EPERM check */
+
+ ret = hid_bpf_input_report(ctx, HID_INPUT_REPORT, args->data, size);
+ args->retval = ret;
+
+ hid_bpf_release_context(ctx);
+
+ return 0;
+}
+
+static const __u8 rdesc[] = {
+ 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */
+ 0x09, 0x32, /* USAGE (Z) */
+ 0x95, 0x01, /* REPORT_COUNT (1) */
+ 0x81, 0x06, /* INPUT (Data,Var,Rel) */
+
+ 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */
+ 0x19, 0x01, /* USAGE_MINIMUM (1) */
+ 0x29, 0x03, /* USAGE_MAXIMUM (3) */
+ 0x15, 0x00, /* LOGICAL_MINIMUM (0) */
+ 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */
+ 0x95, 0x03, /* REPORT_COUNT (3) */
+ 0x75, 0x01, /* REPORT_SIZE (1) */
+ 0x91, 0x02, /* Output (Data,Var,Abs) */
+ 0x95, 0x01, /* REPORT_COUNT (1) */
+ 0x75, 0x05, /* REPORT_SIZE (5) */
+ 0x91, 0x01, /* Output (Cnst,Var,Abs) */
+
+ 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */
+ 0x19, 0x06, /* USAGE_MINIMUM (6) */
+ 0x29, 0x08, /* USAGE_MAXIMUM (8) */
+ 0x15, 0x00, /* LOGICAL_MINIMUM (0) */
+ 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */
+ 0x95, 0x03, /* REPORT_COUNT (3) */
+ 0x75, 0x01, /* REPORT_SIZE (1) */
+ 0xb1, 0x02, /* Feature (Data,Var,Abs) */
+ 0x95, 0x01, /* REPORT_COUNT (1) */
+ 0x75, 0x05, /* REPORT_SIZE (5) */
+ 0x91, 0x01, /* Output (Cnst,Var,Abs) */
+
+ 0xc0, /* END_COLLECTION */
+ 0xc0, /* END_COLLECTION */
+};
+
+SEC("?fmod_ret/hid_bpf_rdesc_fixup")
+int BPF_PROG(hid_rdesc_fixup, struct hid_bpf_ctx *hid_ctx)
+{
+ __u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 4096 /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ callback2_check = data[4];
+
+ /* insert rdesc at offset 73 */
+ __builtin_memcpy(&data[73], rdesc, sizeof(rdesc));
+
+ /* Change Usage Vendor globally */
+ data[4] = 0x42;
+
+ return sizeof(rdesc) + 73;
+}
+
+SEC("?fmod_ret/hid_bpf_device_event")
+int BPF_PROG(hid_test_insert1, struct hid_bpf_ctx *hid_ctx)
+{
+ __u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 4 /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ /* we need to be run first */
+ if (data[2] || data[3])
+ return -1;
+
+ data[1] = 1;
+
+ return 0;
+}
+
+SEC("?fmod_ret/hid_bpf_device_event")
+int BPF_PROG(hid_test_insert2, struct hid_bpf_ctx *hid_ctx)
+{
+ __u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 4 /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ /* after insert0 and before insert2 */
+ if (!data[1] || data[3])
+ return -1;
+
+ data[2] = 2;
+
+ return 0;
+}
+
+SEC("?fmod_ret/hid_bpf_device_event")
+int BPF_PROG(hid_test_insert3, struct hid_bpf_ctx *hid_ctx)
+{
+ __u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 4 /* size */);
+
+ if (!data)
+ return 0; /* EPERM check */
+
+ /* at the end */
+ if (!data[1] || !data[2])
+ return -1;
+
+ data[3] = 3;
+
+ return 0;
+}
diff --git a/tools/testing/selftests/hid/progs/hid_bpf_helpers.h b/tools/testing/selftests/hid/progs/hid_bpf_helpers.h
new file mode 100644
index 000000000000..9cd56821d0f1
--- /dev/null
+++ b/tools/testing/selftests/hid/progs/hid_bpf_helpers.h
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (c) 2022 Benjamin Tissoires
+ */
+
+#ifndef __HID_BPF_HELPERS_H
+#define __HID_BPF_HELPERS_H
+
+/* "undefine" structs and enums in vmlinux.h, because we "override" them below */
+#define hid_bpf_ctx hid_bpf_ctx___not_used
+#define hid_report_type hid_report_type___not_used
+#define hid_class_request hid_class_request___not_used
+#define hid_bpf_attach_flags hid_bpf_attach_flags___not_used
+#define HID_INPUT_REPORT HID_INPUT_REPORT___not_used
+#define HID_OUTPUT_REPORT HID_OUTPUT_REPORT___not_used
+#define HID_FEATURE_REPORT HID_FEATURE_REPORT___not_used
+#define HID_REPORT_TYPES HID_REPORT_TYPES___not_used
+#define HID_REQ_GET_REPORT HID_REQ_GET_REPORT___not_used
+#define HID_REQ_GET_IDLE HID_REQ_GET_IDLE___not_used
+#define HID_REQ_GET_PROTOCOL HID_REQ_GET_PROTOCOL___not_used
+#define HID_REQ_SET_REPORT HID_REQ_SET_REPORT___not_used
+#define HID_REQ_SET_IDLE HID_REQ_SET_IDLE___not_used
+#define HID_REQ_SET_PROTOCOL HID_REQ_SET_PROTOCOL___not_used
+#define HID_BPF_FLAG_NONE HID_BPF_FLAG_NONE___not_used
+#define HID_BPF_FLAG_INSERT_HEAD HID_BPF_FLAG_INSERT_HEAD___not_used
+#define HID_BPF_FLAG_MAX HID_BPF_FLAG_MAX___not_used
+
+#include "vmlinux.h"
+
+#undef hid_bpf_ctx
+#undef hid_report_type
+#undef hid_class_request
+#undef hid_bpf_attach_flags
+#undef HID_INPUT_REPORT
+#undef HID_OUTPUT_REPORT
+#undef HID_FEATURE_REPORT
+#undef HID_REPORT_TYPES
+#undef HID_REQ_GET_REPORT
+#undef HID_REQ_GET_IDLE
+#undef HID_REQ_GET_PROTOCOL
+#undef HID_REQ_SET_REPORT
+#undef HID_REQ_SET_IDLE
+#undef HID_REQ_SET_PROTOCOL
+#undef HID_BPF_FLAG_NONE
+#undef HID_BPF_FLAG_INSERT_HEAD
+#undef HID_BPF_FLAG_MAX
+
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include <linux/const.h>
+
+enum hid_report_type {
+ HID_INPUT_REPORT = 0,
+ HID_OUTPUT_REPORT = 1,
+ HID_FEATURE_REPORT = 2,
+
+ HID_REPORT_TYPES,
+};
+
+struct hid_bpf_ctx {
+ __u32 index;
+ const struct hid_device *hid;
+ __u32 allocated_size;
+ enum hid_report_type report_type;
+ union {
+ __s32 retval;
+ __s32 size;
+ };
+} __attribute__((preserve_access_index));
+
+enum hid_class_request {
+ HID_REQ_GET_REPORT = 0x01,
+ HID_REQ_GET_IDLE = 0x02,
+ HID_REQ_GET_PROTOCOL = 0x03,
+ HID_REQ_SET_REPORT = 0x09,
+ HID_REQ_SET_IDLE = 0x0A,
+ HID_REQ_SET_PROTOCOL = 0x0B,
+};
+
+enum hid_bpf_attach_flags {
+ HID_BPF_FLAG_NONE = 0,
+ HID_BPF_FLAG_INSERT_HEAD = _BITUL(0),
+ HID_BPF_FLAG_MAX,
+};
+
+/* following are kfuncs exported by HID for HID-BPF */
+extern __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx,
+ unsigned int offset,
+ const size_t __sz) __ksym;
+extern int hid_bpf_attach_prog(unsigned int hid_id, int prog_fd, u32 flags) __ksym;
+extern struct hid_bpf_ctx *hid_bpf_allocate_context(unsigned int hid_id) __ksym;
+extern void hid_bpf_release_context(struct hid_bpf_ctx *ctx) __ksym;
+extern int hid_bpf_hw_request(struct hid_bpf_ctx *ctx,
+ __u8 *data,
+ size_t buf__sz,
+ enum hid_report_type type,
+ enum hid_class_request reqtype) __ksym;
+extern int hid_bpf_hw_output_report(struct hid_bpf_ctx *ctx,
+ __u8 *buf, size_t buf__sz) __ksym;
+extern int hid_bpf_input_report(struct hid_bpf_ctx *ctx,
+ enum hid_report_type type,
+ __u8 *data,
+ size_t buf__sz) __ksym;
+
+#endif /* __HID_BPF_HELPERS_H */
diff --git a/tools/testing/selftests/hid/run-hid-tools-tests.sh b/tools/testing/selftests/hid/run-hid-tools-tests.sh
new file mode 100755
index 000000000000..bdae8464da86
--- /dev/null
+++ b/tools/testing/selftests/hid/run-hid-tools-tests.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Runs tests for the HID subsystem
+
+if ! command -v python3 > /dev/null 2>&1; then
+ echo "hid-tools: [SKIP] python3 not installed"
+ exit 77
+fi
+
+if ! python3 -c "import pytest" > /dev/null 2>&1; then
+ echo "hid: [SKIP/ pytest module not installed"
+ exit 77
+fi
+
+if ! python3 -c "import pytest_tap" > /dev/null 2>&1; then
+ echo "hid: [SKIP/ pytest_tap module not installed"
+ exit 77
+fi
+
+if ! python3 -c "import hidtools" > /dev/null 2>&1; then
+ echo "hid: [SKIP/ hid-tools module not installed"
+ exit 77
+fi
+
+TARGET=${TARGET:=.}
+
+echo TAP version 13
+python3 -u -m pytest $PYTEST_XDIST ./tests/$TARGET --tap-stream --udevd
diff --git a/tools/testing/selftests/hid/settings b/tools/testing/selftests/hid/settings
new file mode 100644
index 000000000000..b3cbfc521b10
--- /dev/null
+++ b/tools/testing/selftests/hid/settings
@@ -0,0 +1,3 @@
+# HID tests can be long, so give a little bit more time
+# to them
+timeout=200
diff --git a/tools/testing/selftests/hid/tests/__init__.py b/tools/testing/selftests/hid/tests/__init__.py
new file mode 100644
index 000000000000..c940e9275252
--- /dev/null
+++ b/tools/testing/selftests/hid/tests/__init__.py
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0
+# Just to make sphinx-apidoc document this directory
diff --git a/tools/testing/selftests/hid/tests/base.py b/tools/testing/selftests/hid/tests/base.py
new file mode 100644
index 000000000000..3a465768e507
--- /dev/null
+++ b/tools/testing/selftests/hid/tests/base.py
@@ -0,0 +1,418 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2017 Red Hat, Inc.
+
+import libevdev
+import os
+import pytest
+import shutil
+import subprocess
+import time
+
+import logging
+
+from .base_device import BaseDevice, EvdevMatch, SysfsFile
+from pathlib import Path
+from typing import Final, List, Tuple
+
+logger = logging.getLogger("hidtools.test.base")
+
+# application to matches
+application_matches: Final = {
+ # pyright: ignore
+ "Accelerometer": EvdevMatch(
+ req_properties=[
+ libevdev.INPUT_PROP_ACCELEROMETER,
+ ]
+ ),
+ "Game Pad": EvdevMatch( # in systemd, this is a lot more complex, but that will do
+ requires=[
+ libevdev.EV_ABS.ABS_X,
+ libevdev.EV_ABS.ABS_Y,
+ libevdev.EV_ABS.ABS_RX,
+ libevdev.EV_ABS.ABS_RY,
+ libevdev.EV_KEY.BTN_START,
+ ],
+ excl_properties=[
+ libevdev.INPUT_PROP_ACCELEROMETER,
+ ],
+ ),
+ "Joystick": EvdevMatch( # in systemd, this is a lot more complex, but that will do
+ requires=[
+ libevdev.EV_ABS.ABS_RX,
+ libevdev.EV_ABS.ABS_RY,
+ libevdev.EV_KEY.BTN_START,
+ ],
+ excl_properties=[
+ libevdev.INPUT_PROP_ACCELEROMETER,
+ ],
+ ),
+ "Key": EvdevMatch(
+ requires=[
+ libevdev.EV_KEY.KEY_A,
+ ],
+ excl_properties=[
+ libevdev.INPUT_PROP_ACCELEROMETER,
+ libevdev.INPUT_PROP_DIRECT,
+ libevdev.INPUT_PROP_POINTER,
+ ],
+ ),
+ "Mouse": EvdevMatch(
+ requires=[
+ libevdev.EV_REL.REL_X,
+ libevdev.EV_REL.REL_Y,
+ libevdev.EV_KEY.BTN_LEFT,
+ ],
+ excl_properties=[
+ libevdev.INPUT_PROP_ACCELEROMETER,
+ ],
+ ),
+ "Pad": EvdevMatch(
+ requires=[
+ libevdev.EV_KEY.BTN_0,
+ ],
+ excludes=[
+ libevdev.EV_KEY.BTN_TOOL_PEN,
+ libevdev.EV_KEY.BTN_TOUCH,
+ libevdev.EV_ABS.ABS_DISTANCE,
+ ],
+ excl_properties=[
+ libevdev.INPUT_PROP_ACCELEROMETER,
+ ],
+ ),
+ "Pen": EvdevMatch(
+ requires=[
+ libevdev.EV_KEY.BTN_STYLUS,
+ libevdev.EV_ABS.ABS_X,
+ libevdev.EV_ABS.ABS_Y,
+ ],
+ excl_properties=[
+ libevdev.INPUT_PROP_ACCELEROMETER,
+ ],
+ ),
+ "Stylus": EvdevMatch(
+ requires=[
+ libevdev.EV_KEY.BTN_STYLUS,
+ libevdev.EV_ABS.ABS_X,
+ libevdev.EV_ABS.ABS_Y,
+ ],
+ excl_properties=[
+ libevdev.INPUT_PROP_ACCELEROMETER,
+ ],
+ ),
+ "Touch Pad": EvdevMatch(
+ requires=[
+ libevdev.EV_KEY.BTN_LEFT,
+ libevdev.EV_ABS.ABS_X,
+ libevdev.EV_ABS.ABS_Y,
+ ],
+ excludes=[libevdev.EV_KEY.BTN_TOOL_PEN, libevdev.EV_KEY.BTN_STYLUS],
+ req_properties=[
+ libevdev.INPUT_PROP_POINTER,
+ ],
+ excl_properties=[
+ libevdev.INPUT_PROP_ACCELEROMETER,
+ ],
+ ),
+ "Touch Screen": EvdevMatch(
+ requires=[
+ libevdev.EV_KEY.BTN_TOUCH,
+ libevdev.EV_ABS.ABS_X,
+ libevdev.EV_ABS.ABS_Y,
+ ],
+ excludes=[libevdev.EV_KEY.BTN_TOOL_PEN, libevdev.EV_KEY.BTN_STYLUS],
+ req_properties=[
+ libevdev.INPUT_PROP_DIRECT,
+ ],
+ excl_properties=[
+ libevdev.INPUT_PROP_ACCELEROMETER,
+ ],
+ ),
+}
+
+
+class UHIDTestDevice(BaseDevice):
+ def __init__(self, name, application, rdesc_str=None, rdesc=None, input_info=None):
+ super().__init__(name, application, rdesc_str, rdesc, input_info)
+ self.application_matches = application_matches
+ if name is None:
+ name = f"uhid test {self.__class__.__name__}"
+ if not name.startswith("uhid test "):
+ name = "uhid test " + self.name
+ self.name = name
+
+
+class BaseTestCase:
+ class TestUhid(object):
+ syn_event = libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT) # type: ignore
+ key_event = libevdev.InputEvent(libevdev.EV_KEY) # type: ignore
+ abs_event = libevdev.InputEvent(libevdev.EV_ABS) # type: ignore
+ rel_event = libevdev.InputEvent(libevdev.EV_REL) # type: ignore
+ msc_event = libevdev.InputEvent(libevdev.EV_MSC.MSC_SCAN) # type: ignore
+
+ # List of kernel modules to load before starting the test
+ # if any module is not available (not compiled), the test will skip.
+ # Each element is a tuple '(kernel driver name, kernel module)',
+ # for example ("playstation", "hid-playstation")
+ kernel_modules: List[Tuple[str, str]] = []
+
+ # List of in kernel HID-BPF object files to load
+ # before starting the test
+ # Any existing pre-loaded HID-BPF module will be removed
+ # before the ones in this list will be manually loaded.
+ # Each Element is a tuple '(hid_bpf_object, rdesc_fixup_present)',
+ # for example '("xppen-ArtistPro16Gen2.bpf.o", True)'
+ # If 'rdesc_fixup_present' is True, the test needs to wait
+ # for one unbind and rebind before it can be sure the kernel is
+ # ready
+ hid_bpfs: List[Tuple[str, bool]] = []
+
+ def assertInputEventsIn(self, expected_events, effective_events):
+ effective_events = effective_events.copy()
+ for ev in expected_events:
+ assert ev in effective_events
+ effective_events.remove(ev)
+ return effective_events
+
+ def assertInputEvents(self, expected_events, effective_events):
+ remaining = self.assertInputEventsIn(expected_events, effective_events)
+ assert remaining == []
+
+ @classmethod
+ def debug_reports(cls, reports, uhdev=None, events=None):
+ data = [" ".join([f"{v:02x}" for v in r]) for r in reports]
+
+ if uhdev is not None:
+ human_data = [
+ uhdev.parsed_rdesc.format_report(r, split_lines=True)
+ for r in reports
+ ]
+ try:
+ human_data = [
+ f'\n\t {" " * h.index("/")}'.join(h.split("\n"))
+ for h in human_data
+ ]
+ except ValueError:
+ # '/' not found: not a numbered report
+ human_data = ["\n\t ".join(h.split("\n")) for h in human_data]
+ data = [f"{d}\n\t ====> {h}" for d, h in zip(data, human_data)]
+
+ reports = data
+
+ if len(reports) == 1:
+ print("sending 1 report:")
+ else:
+ print(f"sending {len(reports)} reports:")
+ for report in reports:
+ print("\t", report)
+
+ if events is not None:
+ print("events received:", events)
+
+ def create_device(self):
+ raise Exception("please reimplement me in subclasses")
+
+ def _load_kernel_module(self, kernel_driver, kernel_module):
+ sysfs_path = Path("/sys/bus/hid/drivers")
+ if kernel_driver is not None:
+ sysfs_path /= kernel_driver
+ else:
+ # special case for when testing all available modules:
+ # we don't know beforehand the name of the module from modinfo
+ sysfs_path = Path("/sys/module") / kernel_module.replace("-", "_")
+ if not sysfs_path.exists():
+ ret = subprocess.run(["/usr/sbin/modprobe", kernel_module])
+ if ret.returncode != 0:
+ pytest.skip(
+ f"module {kernel_module} could not be loaded, skipping the test"
+ )
+
+ @pytest.fixture()
+ def load_kernel_module(self):
+ for kernel_driver, kernel_module in self.kernel_modules:
+ self._load_kernel_module(kernel_driver, kernel_module)
+ yield
+
+ def load_hid_bpfs(self):
+ script_dir = Path(os.path.dirname(os.path.realpath(__file__)))
+ root_dir = (script_dir / "../../../../..").resolve()
+ bpf_dir = root_dir / "drivers/hid/bpf/progs"
+
+ udev_hid_bpf = shutil.which("udev-hid-bpf")
+ if not udev_hid_bpf:
+ pytest.skip("udev-hid-bpf not found in $PATH, skipping")
+
+ wait = False
+ for _, rdesc_fixup in self.hid_bpfs:
+ if rdesc_fixup:
+ wait = True
+
+ for hid_bpf, _ in self.hid_bpfs:
+ # We need to start `udev-hid-bpf` in the background
+ # and dispatch uhid events in case the kernel needs
+ # to fetch features on the device
+ process = subprocess.Popen(
+ [
+ "udev-hid-bpf",
+ "--verbose",
+ "add",
+ str(self.uhdev.sys_path),
+ str(bpf_dir / hid_bpf),
+ ],
+ )
+ while process.poll() is None:
+ self.uhdev.dispatch(1)
+
+ if process.poll() != 0:
+ pytest.fail(
+ f"Couldn't insert hid-bpf program '{hid_bpf}', marking the test as failed"
+ )
+
+ if wait:
+ # the HID-BPF program exports a rdesc fixup, so it needs to be
+ # unbound by the kernel and then rebound.
+ # Ensure we get the bound event exactly 2 times (one for the normal
+ # uhid loading, and then the reload from HID-BPF)
+ now = time.time()
+ while self.uhdev.kernel_ready_count < 2 and time.time() - now < 2:
+ self.uhdev.dispatch(1)
+
+ if self.uhdev.kernel_ready_count < 2:
+ pytest.fail(
+ f"Couldn't insert hid-bpf programs, marking the test as failed"
+ )
+
+ def unload_hid_bpfs(self):
+ ret = subprocess.run(
+ ["udev-hid-bpf", "--verbose", "remove", str(self.uhdev.sys_path)],
+ )
+ if ret.returncode != 0:
+ pytest.fail(
+ f"Couldn't unload hid-bpf programs, marking the test as failed"
+ )
+
+ @pytest.fixture()
+ def new_uhdev(self, load_kernel_module):
+ return self.create_device()
+
+ def assertName(self, uhdev):
+ evdev = uhdev.get_evdev()
+ assert uhdev.name in evdev.name
+
+ @pytest.fixture(autouse=True)
+ def context(self, new_uhdev, request):
+ try:
+ with HIDTestUdevRule.instance():
+ with new_uhdev as self.uhdev:
+ for skip_cond in request.node.iter_markers("skip_if_uhdev"):
+ test, message, *rest = skip_cond.args
+
+ if test(self.uhdev):
+ pytest.skip(message)
+
+ self.uhdev.create_kernel_device()
+ now = time.time()
+ while not self.uhdev.is_ready() and time.time() - now < 5:
+ self.uhdev.dispatch(1)
+
+ if self.hid_bpfs:
+ self.load_hid_bpfs()
+
+ if self.uhdev.get_evdev() is None:
+ logger.warning(
+ f"available list of input nodes: (default application is '{self.uhdev.application}')"
+ )
+ logger.warning(self.uhdev.input_nodes)
+ yield
+ if self.hid_bpfs:
+ self.unload_hid_bpfs()
+ self.uhdev = None
+ except PermissionError:
+ pytest.skip("Insufficient permissions, run me as root")
+
+ @pytest.fixture(autouse=True)
+ def check_taint(self):
+ # we are abusing SysfsFile here, it's in /proc, but meh
+ taint_file = SysfsFile("/proc/sys/kernel/tainted")
+ taint = taint_file.int_value
+
+ yield
+
+ assert taint_file.int_value == taint
+
+ def test_creation(self):
+ """Make sure the device gets processed by the kernel and creates
+ the expected application input node.
+
+ If this fail, there is something wrong in the device report
+ descriptors."""
+ uhdev = self.uhdev
+ assert uhdev is not None
+ assert uhdev.get_evdev() is not None
+ self.assertName(uhdev)
+ assert len(uhdev.next_sync_events()) == 0
+ assert uhdev.get_evdev() is not None
+
+
+class HIDTestUdevRule(object):
+ _instance = None
+ """
+ A context-manager compatible class that sets up our udev rules file and
+ deletes it on context exit.
+
+ This class is tailored to our test setup: it only sets up the udev rule
+ on the **second** context and it cleans it up again on the last context
+ removed. This matches the expected pytest setup: we enter a context for
+ the session once, then once for each test (the first of which will
+ trigger the udev rule) and once the last test exited and the session
+ exited, we clean up after ourselves.
+ """
+
+ def __init__(self):
+ self.refs = 0
+ self.rulesfile = None
+
+ def __enter__(self):
+ self.refs += 1
+ if self.refs == 2 and self.rulesfile is None:
+ self.create_udev_rule()
+ self.reload_udev_rules()
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self.refs -= 1
+ if self.refs == 0 and self.rulesfile:
+ os.remove(self.rulesfile.name)
+ self.reload_udev_rules()
+
+ def reload_udev_rules(self):
+ subprocess.run("udevadm control --reload-rules".split())
+ subprocess.run("systemd-hwdb update".split())
+
+ def create_udev_rule(self):
+ import tempfile
+
+ os.makedirs("/run/udev/rules.d", exist_ok=True)
+ with tempfile.NamedTemporaryFile(
+ prefix="91-uhid-test-device-REMOVEME-",
+ suffix=".rules",
+ mode="w+",
+ dir="/run/udev/rules.d",
+ delete=False,
+ ) as f:
+ f.write(
+ """
+KERNELS=="*input*", ATTRS{name}=="*uhid test *", ENV{LIBINPUT_IGNORE_DEVICE}="1"
+KERNELS=="*hid*", ENV{HID_NAME}=="*uhid test *", ENV{HID_BPF_IGNORE_DEVICE}="1"
+KERNELS=="*input*", ATTRS{name}=="*uhid test * System Multi Axis", ENV{ID_INPUT_TOUCHSCREEN}="", ENV{ID_INPUT_SYSTEM_MULTIAXIS}="1"
+"""
+ )
+ self.rulesfile = f
+
+ @classmethod
+ def instance(cls):
+ if not cls._instance:
+ cls._instance = HIDTestUdevRule()
+ return cls._instance
diff --git a/tools/testing/selftests/hid/tests/base_device.py b/tools/testing/selftests/hid/tests/base_device.py
new file mode 100644
index 000000000000..e0515be97f83
--- /dev/null
+++ b/tools/testing/selftests/hid/tests/base_device.py
@@ -0,0 +1,421 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2017 Red Hat, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import fcntl
+import functools
+import libevdev
+import os
+
+try:
+ import pyudev
+except ImportError:
+ raise ImportError("UHID is not supported due to missing pyudev dependency")
+
+import logging
+
+import hidtools.hid as hid
+from hidtools.uhid import UHIDDevice
+from hidtools.util import BusType
+
+from pathlib import Path
+from typing import Any, ClassVar, Dict, List, Optional, Tuple, Type, Union
+
+logger = logging.getLogger("hidtools.device.base_device")
+
+
+class SysfsFile(object):
+ def __init__(self, path):
+ self.path = path
+
+ def __set_value(self, value):
+ with open(self.path, "w") as f:
+ return f.write(f"{value}\n")
+
+ def __get_value(self):
+ with open(self.path) as f:
+ return f.read().strip()
+
+ @property
+ def int_value(self) -> int:
+ return int(self.__get_value())
+
+ @int_value.setter
+ def int_value(self, v: int) -> None:
+ self.__set_value(v)
+
+ @property
+ def str_value(self) -> str:
+ return self.__get_value()
+
+ @str_value.setter
+ def str_value(self, v: str) -> None:
+ self.__set_value(v)
+
+
+class LED(object):
+ def __init__(self, sys_path):
+ self.max_brightness = SysfsFile(sys_path / "max_brightness").int_value
+ self.__brightness = SysfsFile(sys_path / "brightness")
+
+ @property
+ def brightness(self) -> int:
+ return self.__brightness.int_value
+
+ @brightness.setter
+ def brightness(self, value: int) -> None:
+ self.__brightness.int_value = value
+
+
+class PowerSupply(object):
+ """Represents Linux power_supply_class sysfs nodes."""
+
+ def __init__(self, sys_path):
+ self._capacity = SysfsFile(sys_path / "capacity")
+ self._status = SysfsFile(sys_path / "status")
+ self._type = SysfsFile(sys_path / "type")
+
+ @property
+ def capacity(self) -> int:
+ return self._capacity.int_value
+
+ @property
+ def status(self) -> str:
+ return self._status.str_value
+
+ @property
+ def type(self) -> str:
+ return self._type.str_value
+
+
+class HIDIsReady(object):
+ """
+ Companion class that binds to a kernel mechanism
+ and that allows to know when a uhid device is ready or not.
+
+ See :meth:`is_ready` for details.
+ """
+
+ def __init__(self: "HIDIsReady", uhid: UHIDDevice) -> None:
+ self.uhid = uhid
+
+ def is_ready(self: "HIDIsReady") -> bool:
+ """
+ Overwrite in subclasses: should return True or False whether
+ the attached uhid device is ready or not.
+ """
+ return False
+
+
+class UdevHIDIsReady(HIDIsReady):
+ _pyudev_context: ClassVar[Optional[pyudev.Context]] = None
+ _pyudev_monitor: ClassVar[Optional[pyudev.Monitor]] = None
+ _uhid_devices: ClassVar[Dict[int, Tuple[bool, int]]] = {}
+
+ def __init__(self: "UdevHIDIsReady", uhid: UHIDDevice) -> None:
+ super().__init__(uhid)
+ self._init_pyudev()
+
+ @classmethod
+ def _init_pyudev(cls: Type["UdevHIDIsReady"]) -> None:
+ if cls._pyudev_context is None:
+ cls._pyudev_context = pyudev.Context()
+ cls._pyudev_monitor = pyudev.Monitor.from_netlink(cls._pyudev_context)
+ cls._pyudev_monitor.filter_by("hid")
+ cls._pyudev_monitor.start()
+
+ UHIDDevice._append_fd_to_poll(
+ cls._pyudev_monitor.fileno(), cls._cls_udev_event_callback
+ )
+
+ @classmethod
+ def _cls_udev_event_callback(cls: Type["UdevHIDIsReady"]) -> None:
+ if cls._pyudev_monitor is None:
+ return
+ event: pyudev.Device
+ for event in iter(functools.partial(cls._pyudev_monitor.poll, 0.02), None):
+ if event.action not in ["bind", "remove", "unbind"]:
+ return
+
+ logger.debug(f"udev event: {event.action} -> {event}")
+
+ id = int(event.sys_path.strip().split(".")[-1], 16)
+
+ device_ready, count = cls._uhid_devices.get(id, (False, 0))
+
+ ready = event.action == "bind"
+ if not device_ready and ready:
+ count += 1
+ cls._uhid_devices[id] = (ready, count)
+
+ def is_ready(self: "UdevHIDIsReady") -> Tuple[bool, int]:
+ try:
+ return self._uhid_devices[self.uhid.hid_id]
+ except KeyError:
+ return (False, 0)
+
+
+class EvdevMatch(object):
+ def __init__(
+ self: "EvdevMatch",
+ *,
+ requires: List[Any] = [],
+ excludes: List[Any] = [],
+ req_properties: List[Any] = [],
+ excl_properties: List[Any] = [],
+ ) -> None:
+ self.requires = requires
+ self.excludes = excludes
+ self.req_properties = req_properties
+ self.excl_properties = excl_properties
+
+ def is_a_match(self: "EvdevMatch", evdev: libevdev.Device) -> bool:
+ for m in self.requires:
+ if not evdev.has(m):
+ return False
+ for m in self.excludes:
+ if evdev.has(m):
+ return False
+ for p in self.req_properties:
+ if not evdev.has_property(p):
+ return False
+ for p in self.excl_properties:
+ if evdev.has_property(p):
+ return False
+ return True
+
+
+class EvdevDevice(object):
+ """
+ Represents an Evdev node and its properties.
+ This is a stub for the libevdev devices, as they are relying on
+ uevent to get the data, saving us some ioctls to fetch the names
+ and properties.
+ """
+
+ def __init__(self: "EvdevDevice", sysfs: Path) -> None:
+ self.sysfs = sysfs
+ self.event_node: Any = None
+ self.libevdev: Optional[libevdev.Device] = None
+
+ self.uevents = {}
+ # all of the interesting properties are stored in the input uevent, so in the parent
+ # so convert the uevent file of the parent input node into a dict
+ with open(sysfs.parent / "uevent") as f:
+ for line in f.readlines():
+ key, value = line.strip().split("=")
+ self.uevents[key] = value.strip('"')
+
+ # we open all evdev nodes in order to not miss any event
+ self.open()
+
+ @property
+ def name(self: "EvdevDevice") -> str:
+ assert "NAME" in self.uevents
+
+ return self.uevents["NAME"]
+
+ @property
+ def evdev(self: "EvdevDevice") -> Path:
+ return Path("/dev/input") / self.sysfs.name
+
+ def matches_application(
+ self: "EvdevDevice", application: str, matches: Dict[str, EvdevMatch]
+ ) -> bool:
+ if self.libevdev is None:
+ return False
+
+ if application in matches:
+ return matches[application].is_a_match(self.libevdev)
+
+ logger.error(
+ f"application '{application}' is unknown, please update/fix hid-tools"
+ )
+ assert False # hid-tools likely needs an update
+
+ def open(self: "EvdevDevice") -> libevdev.Device:
+ self.event_node = open(self.evdev, "rb")
+ self.libevdev = libevdev.Device(self.event_node)
+
+ assert self.libevdev.fd is not None
+
+ fd = self.libevdev.fd.fileno()
+ flag = fcntl.fcntl(fd, fcntl.F_GETFD)
+ fcntl.fcntl(fd, fcntl.F_SETFL, flag | os.O_NONBLOCK)
+
+ return self.libevdev
+
+ def close(self: "EvdevDevice") -> None:
+ if self.libevdev is not None and self.libevdev.fd is not None:
+ self.libevdev.fd.close()
+ self.libevdev = None
+ if self.event_node is not None:
+ self.event_node.close()
+ self.event_node = None
+
+
+class BaseDevice(UHIDDevice):
+ # default _application_matches that matches nothing. This needs
+ # to be set in the subclasses to have get_evdev() working
+ _application_matches: Dict[str, EvdevMatch] = {}
+
+ def __init__(
+ self,
+ name,
+ application,
+ rdesc_str: Optional[str] = None,
+ rdesc: Optional[Union[hid.ReportDescriptor, str, bytes]] = None,
+ input_info=None,
+ ) -> None:
+ self._kernel_is_ready: HIDIsReady = UdevHIDIsReady(self)
+ if rdesc_str is None and rdesc is None:
+ raise Exception("Please provide at least a rdesc or rdesc_str")
+ super().__init__()
+ if name is None:
+ name = f"uhid gamepad test {self.__class__.__name__}"
+ if input_info is None:
+ input_info = (BusType.USB, 1, 2)
+ self.name = name
+ self.info = input_info
+ self.default_reportID = None
+ self.opened = False
+ self.started = False
+ self.application = application
+ self._input_nodes: Optional[list[EvdevDevice]] = None
+ if rdesc is None:
+ assert rdesc_str is not None
+ self.rdesc = hid.ReportDescriptor.from_human_descr(rdesc_str) # type: ignore
+ else:
+ self.rdesc = rdesc # type: ignore
+
+ @property
+ def power_supply_class(self: "BaseDevice") -> Optional[PowerSupply]:
+ ps = self.walk_sysfs("power_supply", "power_supply/*")
+ if ps is None or len(ps) < 1:
+ return None
+
+ return PowerSupply(ps[0])
+
+ @property
+ def led_classes(self: "BaseDevice") -> List[LED]:
+ leds = self.walk_sysfs("led", "**/max_brightness")
+ if leds is None:
+ return []
+
+ return [LED(led.parent) for led in leds]
+
+ @property
+ def kernel_is_ready(self: "BaseDevice") -> bool:
+ return self._kernel_is_ready.is_ready()[0] and self.started
+
+ @property
+ def kernel_ready_count(self: "BaseDevice") -> int:
+ return self._kernel_is_ready.is_ready()[1]
+
+ @property
+ def input_nodes(self: "BaseDevice") -> List[EvdevDevice]:
+ if self._input_nodes is not None:
+ return self._input_nodes
+
+ if not self.kernel_is_ready or not self.started:
+ return []
+
+ self._input_nodes = [
+ EvdevDevice(path)
+ for path in self.walk_sysfs("input", "input/input*/event*")
+ ]
+ return self._input_nodes
+
+ def match_evdev_rule(self, application, evdev):
+ """Replace this in subclasses if the device has multiple reports
+ of the same type and we need to filter based on the actual evdev
+ node.
+
+ returning True will append the corresponding report to
+ `self.input_nodes[type]`
+ returning False will ignore this report / type combination
+ for the device.
+ """
+ return True
+
+ def open(self):
+ self.opened = True
+
+ def _close_all_opened_evdev(self):
+ if self._input_nodes is not None:
+ for e in self._input_nodes:
+ e.close()
+
+ def __del__(self):
+ self._close_all_opened_evdev()
+
+ def close(self):
+ self.opened = False
+
+ def start(self, flags):
+ self.started = True
+
+ def stop(self):
+ self.started = False
+ self._close_all_opened_evdev()
+
+ def next_sync_events(self, application=None):
+ evdev = self.get_evdev(application)
+ if evdev is not None:
+ return list(evdev.events())
+ return []
+
+ @property
+ def application_matches(self: "BaseDevice") -> Dict[str, EvdevMatch]:
+ return self._application_matches
+
+ @application_matches.setter
+ def application_matches(self: "BaseDevice", data: Dict[str, EvdevMatch]) -> None:
+ self._application_matches = data
+
+ def get_evdev(self, application=None):
+ if application is None:
+ application = self.application
+
+ if len(self.input_nodes) == 0:
+ return None
+
+ assert self._input_nodes is not None
+
+ if len(self._input_nodes) == 1:
+ evdev = self._input_nodes[0]
+ if self.match_evdev_rule(application, evdev.libevdev):
+ return evdev.libevdev
+ else:
+ for _evdev in self._input_nodes:
+ if _evdev.matches_application(application, self.application_matches):
+ if self.match_evdev_rule(application, _evdev.libevdev):
+ return _evdev.libevdev
+
+ def is_ready(self):
+ """Returns whether a UHID device is ready. Can be overwritten in
+ subclasses to add extra conditions on when to consider a UHID
+ device ready. This can be:
+
+ - we need to wait on different types of input devices to be ready
+ (Touch Screen and Pen for example)
+ - we need to have at least 4 LEDs present
+ (len(self.uhdev.leds_classes) == 4)
+ - or any other combinations"""
+ return self.kernel_is_ready
diff --git a/tools/testing/selftests/hid/tests/base_gamepad.py b/tools/testing/selftests/hid/tests/base_gamepad.py
new file mode 100644
index 000000000000..ec74d75767a2
--- /dev/null
+++ b/tools/testing/selftests/hid/tests/base_gamepad.py
@@ -0,0 +1,238 @@
+# SPDX-License-Identifier: GPL-2.0
+import libevdev
+
+from .base_device import BaseDevice
+from hidtools.util import BusType
+
+
+class InvalidHIDCommunication(Exception):
+ pass
+
+
+class GamepadData(object):
+ pass
+
+
+class AxisMapping(object):
+ """Represents a mapping between a HID type
+ and an evdev event"""
+
+ def __init__(self, hid, evdev=None):
+ self.hid = hid.lower()
+
+ if evdev is None:
+ evdev = f"ABS_{hid.upper()}"
+
+ self.evdev = libevdev.evbit("EV_ABS", evdev)
+
+
+class BaseGamepad(BaseDevice):
+ buttons_map = {
+ 1: "BTN_SOUTH",
+ 2: "BTN_EAST",
+ 3: "BTN_C",
+ 4: "BTN_NORTH",
+ 5: "BTN_WEST",
+ 6: "BTN_Z",
+ 7: "BTN_TL",
+ 8: "BTN_TR",
+ 9: "BTN_TL2",
+ 10: "BTN_TR2",
+ 11: "BTN_SELECT",
+ 12: "BTN_START",
+ 13: "BTN_MODE",
+ 14: "BTN_THUMBL",
+ 15: "BTN_THUMBR",
+ }
+
+ axes_map = {
+ "left_stick": {
+ "x": AxisMapping("x"),
+ "y": AxisMapping("y"),
+ },
+ "right_stick": {
+ "x": AxisMapping("z"),
+ "y": AxisMapping("Rz"),
+ },
+ }
+
+ def __init__(self, rdesc, application="Game Pad", name=None, input_info=None):
+ assert rdesc is not None
+ super().__init__(name, application, input_info=input_info, rdesc=rdesc)
+ self.buttons = (1, 2, 3)
+ self._buttons = {}
+ self.left = (127, 127)
+ self.right = (127, 127)
+ self.hat_switch = 15
+ assert self.parsed_rdesc is not None
+
+ self.fields = []
+ for r in self.parsed_rdesc.input_reports.values():
+ if r.application_name == self.application:
+ self.fields.extend([f.usage_name for f in r])
+
+ def store_axes(self, which, gamepad, data):
+ amap = self.axes_map[which]
+ x, y = data
+ setattr(gamepad, amap["x"].hid, x)
+ setattr(gamepad, amap["y"].hid, y)
+
+ def create_report(
+ self,
+ *,
+ left=(None, None),
+ right=(None, None),
+ hat_switch=None,
+ buttons=None,
+ reportID=None,
+ application="Game Pad",
+ ):
+ """
+ Return an input report for this device.
+
+ :param left: a tuple of absolute (x, y) value of the left joypad
+ where ``None`` is "leave unchanged"
+ :param right: a tuple of absolute (x, y) value of the right joypad
+ where ``None`` is "leave unchanged"
+ :param hat_switch: an absolute angular value of the hat switch
+ (expressed in 1/8 of circle, 0 being North, 2 East)
+ where ``None`` is "leave unchanged"
+ :param buttons: a dict of index/bool for the button states,
+ where ``None`` is "leave unchanged"
+ :param reportID: the numeric report ID for this report, if needed
+ :param application: the application used to report the values
+ """
+ if buttons is not None:
+ for i, b in buttons.items():
+ if i not in self.buttons:
+ raise InvalidHIDCommunication(
+ f"button {i} is not part of this {self.application}"
+ )
+ if b is not None:
+ self._buttons[i] = b
+
+ def replace_none_in_tuple(item, default):
+ if item is None:
+ item = (None, None)
+
+ if None in item:
+ if item[0] is None:
+ item = (default[0], item[1])
+ if item[1] is None:
+ item = (item[0], default[1])
+
+ return item
+
+ right = replace_none_in_tuple(right, self.right)
+ self.right = right
+ left = replace_none_in_tuple(left, self.left)
+ self.left = left
+
+ if hat_switch is None:
+ hat_switch = self.hat_switch
+ else:
+ self.hat_switch = hat_switch
+
+ reportID = reportID or self.default_reportID
+
+ gamepad = GamepadData()
+ for i, b in self._buttons.items():
+ gamepad.__setattr__(f"b{i}", int(b) if b is not None else 0)
+
+ self.store_axes("left_stick", gamepad, left)
+ self.store_axes("right_stick", gamepad, right)
+ gamepad.hatswitch = hat_switch # type: ignore ### gamepad is by default empty
+ return super().create_report(
+ gamepad, reportID=reportID, application=application
+ )
+
+ def event(
+ self, *, left=(None, None), right=(None, None), hat_switch=None, buttons=None
+ ):
+ """
+ Send an input event on the default report ID.
+
+ :param left: a tuple of absolute (x, y) value of the left joypad
+ where ``None`` is "leave unchanged"
+ :param right: a tuple of absolute (x, y) value of the right joypad
+ where ``None`` is "leave unchanged"
+ :param hat_switch: an absolute angular value of the hat switch
+ where ``None`` is "leave unchanged"
+ :param buttons: a dict of index/bool for the button states,
+ where ``None`` is "leave unchanged"
+ """
+ r = self.create_report(
+ left=left, right=right, hat_switch=hat_switch, buttons=buttons
+ )
+ self.call_input_event(r)
+ return [r]
+
+
+class JoystickGamepad(BaseGamepad):
+ buttons_map = {
+ 1: "BTN_TRIGGER",
+ 2: "BTN_THUMB",
+ 3: "BTN_THUMB2",
+ 4: "BTN_TOP",
+ 5: "BTN_TOP2",
+ 6: "BTN_PINKIE",
+ 7: "BTN_BASE",
+ 8: "BTN_BASE2",
+ 9: "BTN_BASE3",
+ 10: "BTN_BASE4",
+ 11: "BTN_BASE5",
+ 12: "BTN_BASE6",
+ 13: "BTN_DEAD",
+ }
+
+ axes_map = {
+ "left_stick": {
+ "x": AxisMapping("x"),
+ "y": AxisMapping("y"),
+ },
+ "right_stick": {
+ "x": AxisMapping("rudder"),
+ "y": AxisMapping("throttle"),
+ },
+ }
+
+ def __init__(self, rdesc, application="Joystick", name=None, input_info=None):
+ super().__init__(rdesc, application, name, input_info)
+
+ def create_report(
+ self,
+ *,
+ left=(None, None),
+ right=(None, None),
+ hat_switch=None,
+ buttons=None,
+ reportID=None,
+ application=None,
+ ):
+ """
+ Return an input report for this device.
+
+ :param left: a tuple of absolute (x, y) value of the left joypad
+ where ``None`` is "leave unchanged"
+ :param right: a tuple of absolute (x, y) value of the right joypad
+ where ``None`` is "leave unchanged"
+ :param hat_switch: an absolute angular value of the hat switch
+ where ``None`` is "leave unchanged"
+ :param buttons: a dict of index/bool for the button states,
+ where ``None`` is "leave unchanged"
+ :param reportID: the numeric report ID for this report, if needed
+ :param application: the application for this report, if needed
+ """
+ if application is None:
+ application = "Joystick"
+ return super().create_report(
+ left=left,
+ right=right,
+ hat_switch=hat_switch,
+ buttons=buttons,
+ reportID=reportID,
+ application=application,
+ )
+
+ def store_right_joystick(self, gamepad, data):
+ gamepad.rudder, gamepad.throttle = data
diff --git a/tools/testing/selftests/hid/tests/conftest.py b/tools/testing/selftests/hid/tests/conftest.py
new file mode 100644
index 000000000000..1361ec981db6
--- /dev/null
+++ b/tools/testing/selftests/hid/tests/conftest.py
@@ -0,0 +1,81 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2017 Red Hat, Inc.
+
+import platform
+import pytest
+import re
+import resource
+import subprocess
+from .base import HIDTestUdevRule
+from pathlib import Path
+
+
+# See the comment in HIDTestUdevRule, this doesn't set up but it will clean
+# up once the last test exited.
+@pytest.fixture(autouse=True, scope="session")
+def udev_rules_session_setup():
+ with HIDTestUdevRule.instance():
+ yield
+
+
+@pytest.fixture(autouse=True, scope="session")
+def setup_rlimit():
+ resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
+
+
+@pytest.fixture(autouse=True, scope="session")
+def start_udevd(pytestconfig):
+ if pytestconfig.getoption("udevd"):
+ import subprocess
+
+ with subprocess.Popen("/usr/lib/systemd/systemd-udevd") as proc:
+ yield
+ proc.kill()
+ else:
+ yield
+
+
+def pytest_configure(config):
+ config.addinivalue_line(
+ "markers",
+ "skip_if_uhdev(condition, message): mark test to skip if the condition on the uhdev device is met",
+ )
+
+
+# Generate the list of modules and modaliases
+# for the tests that need to be parametrized with those
+def pytest_generate_tests(metafunc):
+ if "usbVidPid" in metafunc.fixturenames:
+ modules = (
+ Path("/lib/modules/")
+ / platform.uname().release
+ / "kernel"
+ / "drivers"
+ / "hid"
+ )
+
+ modalias_re = re.compile(r"alias:\s+hid:b0003g.*v([0-9a-fA-F]+)p([0-9a-fA-F]+)")
+
+ params = []
+ ids = []
+ for module in modules.glob("*.ko"):
+ p = subprocess.run(
+ ["modinfo", module], capture_output=True, check=True, encoding="utf-8"
+ )
+ for line in p.stdout.split("\n"):
+ m = modalias_re.match(line)
+ if m is not None:
+ vid, pid = m.groups()
+ vid = int(vid, 16)
+ pid = int(pid, 16)
+ params.append([module.name.replace(".ko", ""), vid, pid])
+ ids.append(f"{module.name} {vid:04x}:{pid:04x}")
+ metafunc.parametrize("usbVidPid", params, ids=ids)
+
+
+def pytest_addoption(parser):
+ parser.addoption("--udevd", action="store_true", default=False)
diff --git a/tools/testing/selftests/hid/tests/descriptors_wacom.py b/tools/testing/selftests/hid/tests/descriptors_wacom.py
new file mode 100644
index 000000000000..91c16e005c12
--- /dev/null
+++ b/tools/testing/selftests/hid/tests/descriptors_wacom.py
@@ -0,0 +1,1360 @@
+# SPDX-License-Identifier: GPL-2.0
+
+# fmt: off
+wacom_pth660_v145 = [
+ 0x05, 0x01, # . Usage Page (Desktop),
+ 0x09, 0x02, # . Usage (Mouse),
+ 0xA1, 0x01, # . Collection (Application),
+ 0x85, 0x01, # . Report ID (1),
+ 0x09, 0x01, # . Usage (Pointer),
+ 0xA1, 0x00, # . Collection (Physical),
+ 0x05, 0x09, # . Usage Page (Button),
+ 0x19, 0x01, # . Usage Minimum (01h),
+ 0x29, 0x03, # . Usage Maximum (03h),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0x75, 0x01, # . Report Size (1),
+ 0x95, 0x03, # . Report Count (3),
+ 0x81, 0x02, # . Input (Variable),
+ 0x75, 0x01, # . Report Size (1),
+ 0x95, 0x05, # . Report Count (5),
+ 0x81, 0x03, # . Input (Constant, Variable),
+ 0x05, 0x01, # . Usage Page (Desktop),
+ 0x09, 0x30, # . Usage (X),
+ 0x09, 0x31, # . Usage (Y),
+ 0x15, 0x81, # . Logical Minimum (-127),
+ 0x25, 0x7F, # . Logical Maximum (127),
+ 0x75, 0x08, # . Report Size (8),
+ 0x95, 0x02, # . Report Count (2),
+ 0x81, 0x06, # . Input (Variable, Relative),
+ 0xC0, # . End Collection,
+ 0xC0, # . End Collection,
+ 0x06, 0x0D, 0xFF, # . Usage Page (FF0Dh),
+ 0x09, 0x01, # . Usage (01h),
+ 0xA1, 0x01, # . Collection (Application),
+ 0x85, 0x10, # . Report ID (16),
+ 0x09, 0x20, # . Usage (20h),
+ 0xA1, 0x00, # . Collection (Physical),
+ 0x09, 0x42, # . Usage (42h),
+ 0x09, 0x44, # . Usage (44h),
+ 0x09, 0x5A, # . Usage (5Ah),
+ 0x09, 0x45, # . Usage (45h),
+ 0x09, 0x3C, # . Usage (3Ch),
+ 0x09, 0x32, # . Usage (32h),
+ 0x09, 0x36, # . Usage (36h),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0x75, 0x01, # . Report Size (1),
+ 0x95, 0x07, # . Report Count (7),
+ 0x81, 0x02, # . Input (Variable),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x03, # . Input (Constant, Variable),
+ 0x0A, 0x30, 0x01, # . Usage (0130h),
+ 0x65, 0x11, # . Unit (Centimeter),
+ 0x55, 0x0D, # . Unit Exponent (13),
+ 0x35, 0x00, # . Physical Minimum (0),
+ 0x47, 0x80, 0x57, 0x00, 0x00, # . Physical Maximum (22400),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x27, 0x00, 0xAF, 0x00, 0x00, # . Logical Maximum (44800),
+ 0x75, 0x18, # . Report Size (24),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0x0A, 0x31, 0x01, # . Usage (0131h),
+ 0x47, 0xD0, 0x39, 0x00, 0x00, # . Physical Maximum (14800),
+ 0x27, 0xA0, 0x73, 0x00, 0x00, # . Logical Maximum (29600),
+ 0x81, 0x02, # . Input (Variable),
+ 0x09, 0x30, # . Usage (30h),
+ 0x55, 0x00, # . Unit Exponent (0),
+ 0x65, 0x00, # . Unit,
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x26, 0xFF, 0x1F, # . Logical Maximum (8191), # !!! Errata: Missing Physical Max = 0
+ 0x75, 0x10, # . Report Size (16),
+ 0x81, 0x02, # . Input (Variable),
+ 0x09, 0x3D, # . Usage (3Dh),
+ 0x09, 0x3E, # . Usage (3Eh),
+ 0x65, 0x14, # . Unit (Degrees),
+ 0x55, 0x00, # . Unit Exponent (0),
+ 0x35, 0xC0, # . Physical Minimum (-64),
+ 0x45, 0x3F, # . Physical Maximum (63),
+ 0x15, 0xC0, # . Logical Minimum (-64),
+ 0x25, 0x3F, # . Logical Maximum (63),
+ 0x75, 0x08, # . Report Size (8),
+ 0x95, 0x02, # . Report Count (2),
+ 0x81, 0x02, # . Input (Variable),
+ 0x09, 0x41, # . Usage (41h),
+ 0x65, 0x14, # . Unit (Degrees),
+ 0x55, 0x00, # . Unit Exponent (0),
+ 0x36, 0x4C, 0xFF, # . Physical Minimum (-180),
+ 0x46, 0xB3, 0x00, # . Physical Maximum (179),
+ 0x16, 0x7C, 0xFC, # . Logical Minimum (-900),
+ 0x26, 0x83, 0x03, # . Logical Maximum (899),
+ 0x75, 0x10, # . Report Size (16),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x0A, # . Input (Variable, Wrap),
+ 0x0A, 0x03, 0x0D, # . Usage (0D03h),
+ 0x65, 0x00, # . Unit,
+ 0x55, 0x00, # . Unit Exponent (0),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x26, 0xFF, 0x07, # . Logical Maximum (2047), # !!! Errata: Missing Physical Min/Max = 0
+ 0x75, 0x10, # . Report Size (16),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0x0A, 0x32, 0x01, # . Usage (0132h),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x25, 0x3F, # . Logical Maximum (63),
+ 0x75, 0x08, # . Report Size (8),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0x09, 0x5B, # . Usage (5Bh),
+ 0x09, 0x5C, # . Usage (5Ch),
+ 0x17, 0x00, 0x00, 0x00, 0x80, # . Logical Minimum (-2147483648),
+ 0x27, 0xFF, 0xFF, 0xFF, 0x7F, # . Logical Maximum (2147483647),
+ 0x75, 0x20, # . Report Size (32),
+ 0x95, 0x02, # . Report Count (2),
+ 0x81, 0x02, # . Input (Variable),
+ 0x09, 0x77, # . Usage (77h),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x26, 0xFF, 0x0F, # . Logical Maximum (4095),
+ 0x75, 0x10, # . Report Size (16),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0xC0, # . End Collection,
+ 0x85, 0x11, # . Report ID (17),
+ 0x09, 0x39, # . Usage (39h),
+ 0xA1, 0x00, # . Collection (Physical),
+ 0x1A, 0x10, 0x09, # . Usage Minimum (0910h),
+ 0x2A, 0x17, 0x09, # . Usage Maximum (0917h),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0x75, 0x01, # . Report Size (1),
+ 0x95, 0x08, # . Report Count (8),
+ 0x81, 0x02, # . Input (Variable),
+ 0x1A, 0x40, 0x09, # . Usage Minimum (0940h),
+ 0x2A, 0x47, 0x09, # . Usage Maximum (0947h),
+ 0x81, 0x02, # . Input (Variable),
+ 0x0A, 0x95, 0x09, # . Usage (0995h),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0x95, 0x07, # . Report Count (7),
+ 0x81, 0x03, # . Input (Constant, Variable),
+ 0x0A, 0x38, 0x01, # . Usage (0138h),
+ 0x65, 0x14, # . Unit (Degrees),
+ 0x55, 0x00, # . Unit Exponent (0),
+ 0x35, 0x00, # . Physical Minimum (0),
+ 0x46, 0x67, 0x01, # . Physical Maximum (359),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x25, 0x47, # . Logical Maximum (71),
+ 0x75, 0x07, # . Report Size (7),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x0A, # . Input (Variable, Wrap),
+ 0x0A, 0x39, 0x01, # . Usage (0139h),
+ 0x65, 0x00, # . Unit,
+ 0x55, 0x00, # . Unit Exponent (0),
+ 0x25, 0x01, # . Logical Maximum (1), # !!! Errata: Missing Physical Max = 0
+ 0x75, 0x01, # . Report Size (1),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0x75, 0x08, # . Report Size (8),
+ 0x95, 0x04, # . Report Count (4),
+ 0x81, 0x03, # . Input (Constant, Variable),
+ 0xC0, # . End Collection,
+ 0x85, 0x13, # . Report ID (19),
+ 0x0A, 0x13, 0x10, # . Usage (1013h),
+ 0xA1, 0x00, # . Collection (Physical),
+ 0x0A, 0x3B, 0x04, # . Usage (043Bh),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x25, 0x64, # . Logical Maximum (100),
+ 0x75, 0x07, # . Report Size (7),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0x0A, 0x04, 0x04, # . Usage (0404h),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0x75, 0x01, # . Report Size (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0x0A, 0x52, 0x04, # . Usage (0452h),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0x75, 0x01, # . Report Size (1),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0x95, 0x06, # . Report Count (6),
+ 0x81, 0x03, # . Input (Constant, Variable),
+ 0x0A, 0x54, 0x04, # . Usage (0454h),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0x75, 0x08, # . Report Size (8),
+ 0x95, 0x06, # . Report Count (6),
+ 0x81, 0x03, # . Input (Constant, Variable),
+ 0xC0, # . End Collection,
+ 0x09, 0x0E, # . Usage (0Eh),
+ 0xA1, 0x02, # . Collection (Logical),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x85, 0x02, # . Report ID (2),
+ 0x09, 0x01, # . Usage (01h),
+ 0x75, 0x08, # . Report Size (8),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0x95, 0x01, # . Report Count (1),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x03, # . Report ID (3),
+ 0x0A, 0x03, 0x10, # . Usage (1003h),
+ 0x26, 0xFF, 0x00, # . Logical Maximum (255),
+ 0x95, 0x01, # . Report Count (1),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x04, # . Report ID (4),
+ 0x0A, 0x04, 0x10, # . Usage (1004h),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0x95, 0x01, # . Report Count (1),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x07, # . Report ID (7),
+ 0x0A, 0x09, 0x10, # . Usage (1009h),
+ 0x25, 0x02, # . Logical Maximum (2),
+ 0x95, 0x01, # . Report Count (1),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x95, 0x01, # . Report Count (1),
+ 0xB1, 0x03, # . Feature (Constant, Variable),
+ 0x0A, 0x07, 0x10, # . Usage (1007h),
+ 0x09, 0x00, # . Usage (00h),
+ 0x0A, 0x08, 0x10, # . Usage (1008h),
+ 0x09, 0x00, # . Usage (00h),
+ 0x09, 0x00, # . Usage (00h),
+ 0x09, 0x00, # . Usage (00h),
+ 0x27, 0xFF, 0xFF, 0x00, 0x00, # . Logical Maximum (65535),
+ 0x75, 0x10, # . Report Size (16),
+ 0x95, 0x06, # . Report Count (6),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x75, 0x08, # . Report Size (8),
+ 0x95, 0x01, # . Report Count (1),
+ 0xB1, 0x03, # . Feature (Constant, Variable),
+ 0x85, 0x0C, # . Report ID (12),
+ 0x0A, 0x30, 0x0D, # . Usage (0D30h),
+ 0x0A, 0x31, 0x0D, # . Usage (0D31h),
+ 0x0A, 0x32, 0x0D, # . Usage (0D32h),
+ 0x0A, 0x33, 0x0D, # . Usage (0D33h), # !!! Errata: Missing Non-zero Physical Max
+ 0x65, 0x11, # . Unit (Centimeter),
+ 0x55, 0x0D, # . Unit Exponent (13),
+ 0x75, 0x10, # . Report Size (16),
+ 0x95, 0x04, # . Report Count (4),
+ 0xB1, 0x03, # . Feature (Constant, Variable),
+ 0x85, 0x0D, # . Report ID (13),
+ 0x65, 0x00, # . Unit,
+ 0x55, 0x00, # . Unit Exponent (0),
+ 0x0A, 0x0D, 0x10, # . Usage (100Dh),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0x75, 0x08, # . Report Size (8),
+ 0x95, 0x01, # . Report Count (1),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x14, # . Report ID (20),
+ 0x0A, 0x14, 0x10, # . Usage (1014h),
+ 0x26, 0xFF, 0x00, # . Logical Maximum (255),
+ 0x95, 0x0D, # . Report Count (13),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x31, # . Report ID (49),
+ 0x0A, 0x31, 0x10, # . Usage (1031h),
+ 0x25, 0x64, # . Logical Maximum (100),
+ 0x95, 0x05, # . Report Count (5),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x32, # . Report ID (50),
+ 0x0A, 0x31, 0x10, # . Usage (1031h),
+ 0x25, 0x64, # . Logical Maximum (100),
+ 0x95, 0x01, # . Report Count (1),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x0A, 0x32, 0x10, # . Usage (1032h),
+ 0x25, 0x03, # . Logical Maximum (3),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x34, # . Report ID (52),
+ 0x0A, 0x34, 0x10, # . Usage (1034h),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0x95, 0x04, # . Report Count (4),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x35, # . Report ID (53),
+ 0x0A, 0x35, 0x10, # . Usage (1035h),
+ 0x26, 0xFF, 0x00, # . Logical Maximum (255),
+ 0x95, 0x0A, # . Report Count (10),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x36, # . Report ID (54),
+ 0x0A, 0x35, 0x10, # . Usage (1035h),
+ 0x26, 0xFF, 0x00, # . Logical Maximum (255),
+ 0x96, 0x01, 0x01, # . Report Count (257),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xCC, # . Report ID (204),
+ 0x0A, 0xCC, 0x10, # . Usage (10CCh),
+ 0x26, 0xFF, 0x00, # . Logical Maximum (255),
+ 0x95, 0x02, # . Report Count (2),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0xC0, # . End Collection,
+ 0x0A, 0xAC, 0x10, # . Usage (10ACh),
+ 0xA1, 0x02, # . Collection (Logical),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x26, 0xFF, 0x00, # . Logical Maximum (255),
+ 0x75, 0x08, # . Report Size (8),
+ 0x85, 0xAC, # . Report ID (172),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0xBF, # . Report Count (191),
+ 0x81, 0x02, # . Input (Variable),
+ 0x85, 0x33, # . Report ID (51),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x12, # . Report Count (18),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x64, # . Report ID (100),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x0C, # . Report Count (12),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x15, # . Report ID (21),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x0E, # . Report Count (14),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x12, # . Report ID (18),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x04, # . Report Count (4),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x16, # . Report ID (22),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x0E, # . Report Count (14),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x40, # . Report ID (64),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x01, # . Report Count (1),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x41, # . Report ID (65),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x01, # . Report Count (1),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x42, # . Report ID (66),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x04, # . Report Count (4),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x43, # . Report ID (67),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x0D, # . Report Count (13),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x44, # . Report ID (68),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x3F, # . Report Count (63),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x45, # . Report ID (69),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x20, # . Report Count (32),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x60, # . Report ID (96),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x3F, # . Report Count (63),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x61, # . Report ID (97),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x3E, # . Report Count (62),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x62, # . Report ID (98),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x3E, # . Report Count (62),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0xC0, # . End Collection,
+ 0x85, 0xD0, # . Report ID (208),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x08, 0x00, # . Report Count (8),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xD1, # . Report ID (209),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x04, 0x01, # . Report Count (260),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xD2, # . Report ID (210),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x04, 0x01, # . Report Count (260),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xD3, # . Report ID (211),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x04, 0x00, # . Report Count (4),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xD4, # . Report ID (212),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x04, 0x00, # . Report Count (4),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xD5, # . Report ID (213),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x04, 0x00, # . Report Count (4),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xD6, # . Report ID (214),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x04, 0x00, # . Report Count (4),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xD7, # . Report ID (215),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x08, 0x00, # . Report Count (8),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xD8, # . Report ID (216),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x0C, 0x00, # . Report Count (12),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xD9, # . Report ID (217),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x00, 0x0A, # . Report Count (2560),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xDA, # . Report ID (218),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x04, 0x04, # . Report Count (1028),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xDB, # . Report ID (219),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x06, 0x00, # . Report Count (6),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xDC, # . Report ID (220),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x02, 0x00, # . Report Count (2),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xDD, # . Report ID (221),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x04, 0x00, # . Report Count (4),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xDE, # . Report ID (222),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x04, 0x00, # . Report Count (4),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xDF, # . Report ID (223),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x22, 0x00, # . Report Count (34),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xE0, # . Report ID (224),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x01, 0x00, # . Report Count (1),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xE1, # . Report ID (225),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x02, 0x00, # . Report Count (2),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xE2, # . Report ID (226),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x02, 0x00, # . Report Count (2),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xE3, # . Report ID (227),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x02, 0x00, # . Report Count (2),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xE4, # . Report ID (228),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0xFF, 0x01, # . Report Count (511),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0xC0 # . End Collection
+]
+# fmt: on
+
+# Report ID (20), Usage (1014h), Report Count (13) -> 15
+wacom_pth660_v150 = wacom_pth660_v145.copy()
+wacom_pth660_v150[0x2CB] = 0x0F
+
+# fmt: off
+wacom_pth860_v145 = [
+ 0x05, 0x01, # . Usage Page (Desktop),
+ 0x09, 0x02, # . Usage (Mouse),
+ 0xA1, 0x01, # . Collection (Application),
+ 0x85, 0x01, # . Report ID (1),
+ 0x09, 0x01, # . Usage (Pointer),
+ 0xA1, 0x00, # . Collection (Physical),
+ 0x05, 0x09, # . Usage Page (Button),
+ 0x19, 0x01, # . Usage Minimum (01h),
+ 0x29, 0x03, # . Usage Maximum (03h),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0x75, 0x01, # . Report Size (1),
+ 0x95, 0x03, # . Report Count (3),
+ 0x81, 0x02, # . Input (Variable),
+ 0x95, 0x05, # . Report Count (5),
+ 0x81, 0x03, # . Input (Constant, Variable),
+ 0x05, 0x01, # . Usage Page (Desktop),
+ 0x09, 0x30, # . Usage (X),
+ 0x09, 0x31, # . Usage (Y),
+ 0x15, 0x80, # . Logical Minimum (-128),
+ 0x25, 0x7F, # . Logical Maximum (127),
+ 0x75, 0x08, # . Report Size (8),
+ 0x95, 0x02, # . Report Count (2),
+ 0x81, 0x06, # . Input (Variable, Relative),
+ 0xC0, # . End Collection,
+ 0xC0, # . End Collection,
+ 0x06, 0x0D, 0xFF, # . Usage Page (FF0Dh),
+ 0x09, 0x01, # . Usage (01h),
+ 0xA1, 0x01, # . Collection (Application),
+ 0x85, 0x10, # . Report ID (16),
+ 0x09, 0x20, # . Usage (20h),
+ 0xA1, 0x00, # . Collection (Physical),
+ 0x09, 0x42, # . Usage (42h),
+ 0x09, 0x44, # . Usage (44h),
+ 0x09, 0x5A, # . Usage (5Ah),
+ 0x09, 0x45, # . Usage (45h),
+ 0x09, 0x3C, # . Usage (3Ch),
+ 0x09, 0x32, # . Usage (32h),
+ 0x09, 0x36, # . Usage (36h),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0x75, 0x01, # . Report Size (1),
+ 0x95, 0x07, # . Report Count (7),
+ 0x81, 0x02, # . Input (Variable),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x03, # . Input (Constant, Variable),
+ 0x0A, 0x30, 0x01, # . Usage (0130h),
+ 0x65, 0x11, # . Unit (Centimeter),
+ 0x55, 0x0D, # . Unit Exponent (13),
+ 0x35, 0x00, # . Physical Minimum (0),
+ 0x47, 0x7C, 0x79, 0x00, 0x00, # . Physical Maximum (31100),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x27, 0xF8, 0xF2, 0x00, 0x00, # . Logical Maximum (62200),
+ 0x75, 0x18, # . Report Size (24),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0x0A, 0x31, 0x01, # . Usage (0131h),
+ 0x47, 0x60, 0x54, 0x00, 0x00, # . Physical Maximum (21600),
+ 0x27, 0xC0, 0xA8, 0x00, 0x00, # . Logical Maximum (43200),
+ 0x81, 0x02, # . Input (Variable),
+ 0x09, 0x30, # . Usage (30h), # !!! Errata: Missing Physical Max = 0
+ 0x55, 0x00, # . Unit Exponent (0),
+ 0x65, 0x00, # . Unit,
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x26, 0xFF, 0x1F, # . Logical Maximum (8191),
+ 0x75, 0x10, # . Report Size (16),
+ 0x81, 0x02, # . Input (Variable),
+ 0x09, 0x3D, # . Usage (3Dh),
+ 0x09, 0x3E, # . Usage (3Eh),
+ 0x65, 0x14, # . Unit (Degrees),
+ 0x55, 0x00, # . Unit Exponent (0),
+ 0x35, 0xC0, # . Physical Minimum (-64),
+ 0x45, 0x3F, # . Physical Maximum (63),
+ 0x15, 0xC0, # . Logical Minimum (-64),
+ 0x25, 0x3F, # . Logical Maximum (63),
+ 0x75, 0x08, # . Report Size (8),
+ 0x95, 0x02, # . Report Count (2),
+ 0x81, 0x02, # . Input (Variable),
+ 0x09, 0x41, # . Usage (41h),
+ 0x65, 0x14, # . Unit (Degrees),
+ 0x55, 0x00, # . Unit Exponent (0),
+ 0x36, 0x4C, 0xFF, # . Physical Minimum (-180),
+ 0x46, 0xB3, 0x00, # . Physical Maximum (179),
+ 0x16, 0x7C, 0xFC, # . Logical Minimum (-900),
+ 0x26, 0x83, 0x03, # . Logical Maximum (899),
+ 0x75, 0x10, # . Report Size (16),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x0A, # . Input (Variable, Wrap),
+ 0x0A, 0x03, 0x0D, # . Usage (0D03h),
+ 0x65, 0x00, # . Unit,
+ 0x55, 0x00, # . Unit Exponent (0),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x26, 0xFF, 0x07, # . Logical Maximum (2047), # !!! Errata: Missing Physical Min/Max = 0
+ 0x75, 0x10, # . Report Size (16),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0x0A, 0x32, 0x01, # . Usage (0132h),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x25, 0x3F, # . Logical Maximum (63),
+ 0x75, 0x08, # . Report Size (8),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0x09, 0x5B, # . Usage (5Bh),
+ 0x09, 0x5C, # . Usage (5Ch),
+ 0x17, 0x00, 0x00, 0x00, 0x80, # . Logical Minimum (-2147483648),
+ 0x27, 0xFF, 0xFF, 0xFF, 0x7F, # . Logical Maximum (2147483647),
+ 0x75, 0x20, # . Report Size (32),
+ 0x95, 0x02, # . Report Count (2),
+ 0x81, 0x02, # . Input (Variable),
+ 0x09, 0x77, # . Usage (77h),
+ 0x16, 0x00, 0x00, # . Logical Minimum (0),
+ 0x26, 0xFF, 0x0F, # . Logical Maximum (4095),
+ 0x75, 0x10, # . Report Size (16),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0xC0, # . End Collection,
+ 0x85, 0x11, # . Report ID (17),
+ 0x09, 0x39, # . Usage (39h),
+ 0xA1, 0x00, # . Collection (Physical),
+ 0x1A, 0x10, 0x09, # . Usage Minimum (0910h),
+ 0x2A, 0x17, 0x09, # . Usage Maximum (0917h),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0x75, 0x01, # . Report Size (1),
+ 0x95, 0x08, # . Report Count (8),
+ 0x81, 0x02, # . Input (Variable),
+ 0x1A, 0x40, 0x09, # . Usage Minimum (0940h),
+ 0x2A, 0x47, 0x09, # . Usage Maximum (0947h),
+ 0x81, 0x02, # . Input (Variable),
+ 0x0A, 0x95, 0x09, # . Usage (0995h),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0x95, 0x07, # . Report Count (7),
+ 0x81, 0x03, # . Input (Constant, Variable),
+ 0x0A, 0x38, 0x01, # . Usage (0138h),
+ 0x65, 0x14, # . Unit (Degrees),
+ 0x55, 0x00, # . Unit Exponent (0),
+ 0x35, 0x00, # . Physical Minimum (0),
+ 0x46, 0x67, 0x01, # . Physical Maximum (359),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x25, 0x47, # . Logical Maximum (71),
+ 0x75, 0x07, # . Report Size (7),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x0A, # . Input (Variable, Wrap),
+ 0x0A, 0x39, 0x01, # . Usage (0139h),
+ 0x65, 0x00, # . Unit,
+ 0x55, 0x00, # . Unit Exponent (0),
+ 0x25, 0x01, # . Logical Maximum (1), # !!! Errata: Missing Physical Max = 0
+ 0x75, 0x01, # . Report Size (1),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0x75, 0x08, # . Report Size (8),
+ 0x95, 0x04, # . Report Count (4),
+ 0x81, 0x03, # . Input (Constant, Variable),
+ 0xC0, # . End Collection,
+ 0x85, 0x13, # . Report ID (19),
+ 0x0A, 0x13, 0x10, # . Usage (1013h),
+ 0xA1, 0x00, # . Collection (Physical),
+ 0x0A, 0x3B, 0x04, # . Usage (043Bh),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x25, 0x64, # . Logical Maximum (100),
+ 0x75, 0x07, # . Report Size (7),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0x0A, 0x04, 0x04, # . Usage (0404h),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0x75, 0x01, # . Report Size (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0x0A, 0x52, 0x04, # . Usage (0452h),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0x75, 0x01, # . Report Size (1),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0x95, 0x06, # . Report Count (6),
+ 0x81, 0x03, # . Input (Constant, Variable),
+ 0x0A, 0x54, 0x04, # . Usage (0454h),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0x75, 0x08, # . Report Size (8),
+ 0x95, 0x06, # . Report Count (6),
+ 0x81, 0x03, # . Input (Constant, Variable),
+ 0xC0, # . End Collection,
+ 0x09, 0x0E, # . Usage (0Eh),
+ 0xA1, 0x02, # . Collection (Logical),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x85, 0x02, # . Report ID (2),
+ 0x09, 0x01, # . Usage (01h),
+ 0x75, 0x08, # . Report Size (8),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0x95, 0x01, # . Report Count (1),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x03, # . Report ID (3),
+ 0x0A, 0x03, 0x10, # . Usage (1003h),
+ 0x26, 0xFF, 0x00, # . Logical Maximum (255),
+ 0x95, 0x01, # . Report Count (1),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x04, # . Report ID (4),
+ 0x0A, 0x04, 0x10, # . Usage (1004h),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0x95, 0x01, # . Report Count (1),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x07, # . Report ID (7),
+ 0x0A, 0x09, 0x10, # . Usage (1009h),
+ 0x25, 0x02, # . Logical Maximum (2),
+ 0x95, 0x01, # . Report Count (1),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x95, 0x01, # . Report Count (1),
+ 0xB1, 0x03, # . Feature (Constant, Variable),
+ 0x0A, 0x07, 0x10, # . Usage (1007h),
+ 0x09, 0x00, # . Usage (00h),
+ 0x0A, 0x08, 0x10, # . Usage (1008h),
+ 0x09, 0x00, # . Usage (00h),
+ 0x09, 0x00, # . Usage (00h),
+ 0x09, 0x00, # . Usage (00h),
+ 0x27, 0xFF, 0xFF, 0x00, 0x00, # . Logical Maximum (65535),
+ 0x75, 0x10, # . Report Size (16),
+ 0x95, 0x06, # . Report Count (6),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x75, 0x08, # . Report Size (8),
+ 0x95, 0x01, # . Report Count (1),
+ 0xB1, 0x03, # . Feature (Constant, Variable),
+ 0x85, 0x0C, # . Report ID (12),
+ 0x0A, 0x30, 0x0D, # . Usage (0D30h),
+ 0x0A, 0x31, 0x0D, # . Usage (0D31h),
+ 0x0A, 0x32, 0x0D, # . Usage (0D32h),
+ 0x0A, 0x33, 0x0D, # . Usage (0D33h), # !!! Errata: Missing Non-zero Physical Max
+ 0x65, 0x11, # . Unit (Centimeter),
+ 0x55, 0x0D, # . Unit Exponent (13),
+ 0x75, 0x10, # . Report Size (16),
+ 0x95, 0x04, # . Report Count (4),
+ 0xB1, 0x03, # . Feature (Constant, Variable),
+ 0x85, 0x0D, # . Report ID (13),
+ 0x65, 0x00, # . Unit,
+ 0x55, 0x00, # . Unit Exponent (0),
+ 0x0A, 0x0D, 0x10, # . Usage (100Dh),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0x75, 0x08, # . Report Size (8),
+ 0x95, 0x01, # . Report Count (1),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x14, # . Report ID (20),
+ 0x0A, 0x14, 0x10, # . Usage (1014h),
+ 0x26, 0xFF, 0x00, # . Logical Maximum (255),
+ 0x95, 0x0D, # . Report Count (13),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x31, # . Report ID (49),
+ 0x0A, 0x31, 0x10, # . Usage (1031h),
+ 0x25, 0x64, # . Logical Maximum (100),
+ 0x95, 0x05, # . Report Count (5),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x32, # . Report ID (50),
+ 0x0A, 0x31, 0x10, # . Usage (1031h),
+ 0x25, 0x64, # . Logical Maximum (100),
+ 0x95, 0x01, # . Report Count (1),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x0A, 0x32, 0x10, # . Usage (1032h),
+ 0x25, 0x03, # . Logical Maximum (3),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x34, # . Report ID (52),
+ 0x0A, 0x34, 0x10, # . Usage (1034h),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0x95, 0x04, # . Report Count (4),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x35, # . Report ID (53),
+ 0x0A, 0x35, 0x10, # . Usage (1035h),
+ 0x26, 0xFF, 0x00, # . Logical Maximum (255),
+ 0x95, 0x0A, # . Report Count (10),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x36, # . Report ID (54),
+ 0x0A, 0x35, 0x10, # . Usage (1035h),
+ 0x26, 0xFF, 0x00, # . Logical Maximum (255),
+ 0x96, 0x01, 0x01, # . Report Count (257),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xCC, # . Report ID (204),
+ 0x0A, 0xCC, 0x10, # . Usage (10CCh),
+ 0x26, 0xFF, 0x00, # . Logical Maximum (255),
+ 0x95, 0x02, # . Report Count (2),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0xC0, # . End Collection,
+ 0x0A, 0xAC, 0x10, # . Usage (10ACh),
+ 0xA1, 0x02, # . Collection (Logical),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x26, 0xFF, 0x00, # . Logical Maximum (255),
+ 0x75, 0x08, # . Report Size (8),
+ 0x85, 0xAC, # . Report ID (172),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0xBF, # . Report Count (191),
+ 0x81, 0x02, # . Input (Variable),
+ 0x85, 0x33, # . Report ID (51),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x12, # . Report Count (18),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x64, # . Report ID (100),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x0C, # . Report Count (12),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x15, # . Report ID (21),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x0E, # . Report Count (14),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x12, # . Report ID (18),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x04, # . Report Count (4),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x16, # . Report ID (22),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x0E, # . Report Count (14),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x40, # . Report ID (64),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x01, # . Report Count (1),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x41, # . Report ID (65),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x01, # . Report Count (1),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x42, # . Report ID (66),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x04, # . Report Count (4),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x43, # . Report ID (67),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x0D, # . Report Count (13),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x44, # . Report ID (68),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x3F, # . Report Count (63),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x45, # . Report ID (69),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x20, # . Report Count (32),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x60, # . Report ID (96),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x3F, # . Report Count (63),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x61, # . Report ID (97),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x3E, # . Report Count (62),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x62, # . Report ID (98),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x3E, # . Report Count (62),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0xC0, # . End Collection,
+ 0x85, 0xD0, # . Report ID (208),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x08, 0x00, # . Report Count (8),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xD1, # . Report ID (209),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x04, 0x01, # . Report Count (260),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xD2, # . Report ID (210),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x04, 0x01, # . Report Count (260),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xD3, # . Report ID (211),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x04, 0x00, # . Report Count (4),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xD4, # . Report ID (212),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x04, 0x00, # . Report Count (4),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xD5, # . Report ID (213),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x04, 0x00, # . Report Count (4),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xD6, # . Report ID (214),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x04, 0x00, # . Report Count (4),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xD7, # . Report ID (215),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x08, 0x00, # . Report Count (8),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xD8, # . Report ID (216),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x0C, 0x00, # . Report Count (12),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xD9, # . Report ID (217),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x00, 0x0A, # . Report Count (2560),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xDA, # . Report ID (218),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x04, 0x04, # . Report Count (1028),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xDB, # . Report ID (219),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x06, 0x00, # . Report Count (6),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xDC, # . Report ID (220),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x02, 0x00, # . Report Count (2),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xDD, # . Report ID (221),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x04, 0x00, # . Report Count (4),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xDE, # . Report ID (222),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x04, 0x00, # . Report Count (4),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xDF, # . Report ID (223),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x22, 0x00, # . Report Count (34),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xE0, # . Report ID (224),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x01, 0x00, # . Report Count (1),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xE1, # . Report ID (225),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x02, 0x00, # . Report Count (2),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xE2, # . Report ID (226),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x02, 0x00, # . Report Count (2),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xE3, # . Report ID (227),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x02, 0x00, # . Report Count (2),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xE4, # . Report ID (228),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0xFF, 0x01, # . Report Count (511),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0xC0 # . End Collection
+]
+# fmt: on
+
+# Report ID (20), Usage (1014h), Report Count (13) -> 15
+wacom_pth860_v150 = wacom_pth860_v145.copy()
+wacom_pth860_v150[0x2CA] = 0x0F
+
+# fmt: off
+wacom_pth460_v105 = [
+ 0x06, 0x0D, 0xFF, # . Usage Page (FF0Dh),
+ 0x09, 0x01, # . Usage (01h),
+ 0xA1, 0x01, # . Collection (Application),
+ 0x85, 0x10, # . Report ID (16),
+ 0x09, 0x20, # . Usage (20h),
+ 0x35, 0x00, # . Physical Minimum (0),
+ 0x45, 0x00, # . Physical Maximum (0),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0xA1, 0x00, # . Collection (Physical),
+ 0x09, 0x42, # . Usage (42h),
+ 0x09, 0x44, # . Usage (44h),
+ 0x09, 0x5A, # . Usage (5Ah),
+ 0x09, 0x45, # . Usage (45h),
+ 0x09, 0x3C, # . Usage (3Ch),
+ 0x09, 0x32, # . Usage (32h),
+ 0x09, 0x36, # . Usage (36h),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0x75, 0x01, # . Report Size (1),
+ 0x95, 0x07, # . Report Count (7),
+ 0x81, 0x02, # . Input (Variable),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x03, # . Input (Constant, Variable),
+ 0x0A, 0x30, 0x01, # . Usage (0130h),
+ 0x65, 0x11, # . Unit (Centimeter),
+ 0x55, 0x0D, # . Unit Exponent (13),
+ 0x47, 0x58, 0x3E, 0x00, 0x00, # . Physical Maximum (15960),
+ 0x27, 0xB0, 0x7C, 0x00, 0x00, # . Logical Maximum (31920),
+ 0x75, 0x18, # . Report Size (24),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0x0A, 0x31, 0x01, # . Usage (0131h),
+ 0x47, 0xF7, 0x26, 0x00, 0x00, # . Physical Maximum (9975),
+ 0x27, 0xEE, 0x4D, 0x00, 0x00, # . Logical Maximum (19950),
+ 0x81, 0x02, # . Input (Variable),
+ 0x09, 0x30, # . Usage (30h),
+ 0x55, 0x00, # . Unit Exponent (0),
+ 0x65, 0x00, # . Unit,
+ 0x26, 0xFF, 0x1F, # . Logical Maximum (8191), # !!! Errata: Missing Physical Max = 0
+ 0x75, 0x10, # . Report Size (16),
+ 0x81, 0x02, # . Input (Variable),
+ 0x09, 0x3D, # . Usage (3Dh),
+ 0x09, 0x3E, # . Usage (3Eh),
+ 0x65, 0x14, # . Unit (Degrees),
+ 0x55, 0x00, # . Unit Exponent (0),
+ 0x35, 0xC0, # . Physical Minimum (-64),
+ 0x45, 0x3F, # . Physical Maximum (63),
+ 0x15, 0xC0, # . Logical Minimum (-64),
+ 0x25, 0x3F, # . Logical Maximum (63),
+ 0x75, 0x08, # . Report Size (8),
+ 0x95, 0x02, # . Report Count (2),
+ 0x81, 0x02, # . Input (Variable),
+ 0x09, 0x41, # . Usage (41h),
+ 0x65, 0x14, # . Unit (Degrees),
+ 0x55, 0x00, # . Unit Exponent (0),
+ 0x36, 0x4C, 0xFF, # . Physical Minimum (-180),
+ 0x46, 0xB3, 0x00, # . Physical Maximum (179),
+ 0x16, 0x7C, 0xFC, # . Logical Minimum (-900),
+ 0x26, 0x83, 0x03, # . Logical Maximum (899),
+ 0x75, 0x10, # . Report Size (16),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x0A, # . Input (Variable, Wrap),
+ 0x0A, 0x03, 0x0D, # . Usage (0D03h),
+ 0x65, 0x00, # . Unit,
+ 0x55, 0x00, # . Unit Exponent (0),
+ 0x35, 0x00, # . Physical Minimum (0),
+ 0x45, 0x00, # . Physical Maximum (0),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x26, 0xFF, 0x07, # . Logical Maximum (2047),
+ 0x81, 0x02, # . Input (Variable),
+ 0x0A, 0x32, 0x01, # . Usage (0132h),
+ 0x25, 0x3F, # . Logical Maximum (63),
+ 0x75, 0x08, # . Report Size (8),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0x09, 0x5B, # . Usage (5Bh),
+ 0x09, 0x5C, # . Usage (5Ch),
+ 0x17, 0x00, 0x00, 0x00, 0x80, # . Logical Minimum (-2147483648),
+ 0x27, 0xFF, 0xFF, 0xFF, 0x7F, # . Logical Maximum (2147483647),
+ 0x75, 0x20, # . Report Size (32),
+ 0x95, 0x02, # . Report Count (2),
+ 0x81, 0x02, # . Input (Variable),
+ 0x09, 0x77, # . Usage (77h),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x26, 0xFF, 0x0F, # . Logical Maximum (4095),
+ 0x75, 0x10, # . Report Size (16),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0xC0, # . End Collection,
+ 0x85, 0x11, # . Report ID (17),
+ 0x65, 0x00, # . Unit,
+ 0x55, 0x00, # . Unit Exponent (0),
+ 0x35, 0x00, # . Physical Minimum (0),
+ 0x45, 0x00, # . Physical Maximum (0),
+ 0x09, 0x39, # . Usage (39h),
+ 0xA1, 0x00, # . Collection (Physical),
+ 0x09, 0x39, # . Usage (39h),
+ 0xA1, 0x00, # . Collection (Physical),
+ 0x35, 0x00, # . Physical Minimum (0),
+ 0x45, 0x00, # . Physical Maximum (0),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x1A, 0x10, 0x09, # . Usage Minimum (0910h),
+ 0x2A, 0x15, 0x09, # . Usage Maximum (0915h),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0x75, 0x01, # . Report Size (1),
+ 0x95, 0x06, # . Report Count (6),
+ 0x81, 0x02, # . Input (Variable),
+ 0x95, 0x02, # . Report Count (2),
+ 0x81, 0x03, # . Input (Constant, Variable),
+ 0xC0, # . End Collection,
+ 0x75, 0x08, # . Report Size (8),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x03, # . Input (Constant, Variable),
+ 0x09, 0x39, # . Usage (39h),
+ 0xA1, 0x00, # . Collection (Physical),
+ 0x35, 0x00, # . Physical Minimum (0),
+ 0x45, 0x00, # . Physical Maximum (0),
+ 0x0A, 0x95, 0x09, # . Usage (0995h),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0x75, 0x01, # . Report Size (1),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0x95, 0x07, # . Report Count (7),
+ 0x81, 0x03, # . Input (Constant, Variable),
+ 0xC0, # . End Collection,
+ 0x09, 0x39, # . Usage (39h),
+ 0xA1, 0x00, # . Collection (Physical),
+ 0x35, 0x00, # . Physical Minimum (0),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x0A, 0x38, 0x01, # . Usage (0138h),
+ 0x65, 0x14, # . Unit (Degrees),
+ 0x55, 0x00, # . Unit Exponent (0),
+ 0x35, 0x00, # . Physical Minimum (0),
+ 0x46, 0x67, 0x01, # . Physical Maximum (359),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x25, 0x47, # . Logical Maximum (71),
+ 0x75, 0x07, # . Report Size (7),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x4A, # . Input (Variable, Wrap, Null State),
+ 0x0A, 0x39, 0x01, # . Usage (0139h),
+ 0x65, 0x00, # . Unit,
+ 0x55, 0x00, # . Unit Exponent (0),
+ 0x45, 0x00, # . Physical Maximum (0),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0x75, 0x01, # . Report Size (1),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0xC0, # . End Collection,
+ 0x75, 0x08, # . Report Size (8),
+ 0x95, 0x04, # . Report Count (4),
+ 0x81, 0x03, # . Input (Constant, Variable),
+ 0xC0, # . End Collection,
+ 0x85, 0x13, # . Report ID (19),
+ 0x65, 0x00, # . Unit,
+ 0x55, 0x00, # . Unit Exponent (0),
+ 0x35, 0x00, # . Physical Minimum (0),
+ 0x45, 0x00, # . Physical Maximum (0),
+ 0x0A, 0x13, 0x10, # . Usage (1013h),
+ 0xA1, 0x00, # . Collection (Physical),
+ 0x0A, 0x13, 0x10, # . Usage (1013h),
+ 0xA1, 0x00, # . Collection (Physical),
+ 0x35, 0x00, # . Physical Minimum (0),
+ 0x45, 0x00, # . Physical Maximum (0),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x0A, 0x3B, 0x04, # . Usage (043Bh),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x25, 0x64, # . Logical Maximum (100),
+ 0x75, 0x07, # . Report Size (7),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0x0A, 0x04, 0x04, # . Usage (0404h),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0x75, 0x01, # . Report Size (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0xC0, # . End Collection,
+ 0x0A, 0x13, 0x10, # . Usage (1013h),
+ 0xA1, 0x00, # . Collection (Physical),
+ 0x35, 0x00, # . Physical Minimum (0),
+ 0x45, 0x00, # . Physical Maximum (0),
+ 0x0A, 0x52, 0x04, # . Usage (0452h),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0x75, 0x01, # . Report Size (1),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0x0A, 0x41, 0x04, # . Usage (0441h),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x25, 0x07, # . Logical Maximum (7),
+ 0x75, 0x03, # . Report Size (3),
+ 0x95, 0x02, # . Report Count (2),
+ 0x81, 0x02, # . Input (Variable),
+ 0x0A, 0x54, 0x04, # . Usage (0454h),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0x75, 0x01, # . Report Size (1),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0xC0, # . End Collection,
+ 0x0A, 0x13, 0x10, # . Usage (1013h),
+ 0xA1, 0x00, # . Collection (Physical),
+ 0x35, 0x00, # . Physical Minimum (0),
+ 0x45, 0x00, # . Physical Maximum (0),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x0A, 0x3C, 0x04, # . Usage (043Ch),
+ 0x55, 0x00, # . Unit Exponent (0),
+ 0x65, 0x00, # . Unit,
+ 0x15, 0xFB, # . Logical Minimum (-5),
+ 0x25, 0x32, # . Logical Maximum (50),
+ 0x75, 0x08, # . Report Size (8),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0xC0, # . End Collection,
+ 0x0A, 0x13, 0x10, # . Usage (1013h),
+ 0xA1, 0x00, # . Collection (Physical),
+ 0x35, 0x00, # . Physical Minimum (0),
+ 0x45, 0x00, # . Physical Maximum (0),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x0A, 0x3D, 0x04, # . Usage (043Dh),
+ 0x55, 0x00, # . Unit Exponent (0),
+ 0x65, 0x00, # . Unit,
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x26, 0xFF, 0x0F, # . Logical Maximum (4095),
+ 0x75, 0x10, # . Report Size (16),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0xC0, # . End Collection,
+ 0x75, 0x08, # . Report Size (8),
+ 0x95, 0x03, # . Report Count (3),
+ 0x81, 0x03, # . Input (Constant, Variable),
+ 0xC0, # . End Collection,
+ 0x09, 0x0E, # . Usage (0Eh),
+ 0xA1, 0x02, # . Collection (Logical),
+ 0x85, 0x02, # . Report ID (2),
+ 0x0A, 0x02, 0x10, # . Usage (1002h),
+ 0x15, 0x02, # . Logical Minimum (2),
+ 0x25, 0x02, # . Logical Maximum (2),
+ 0x75, 0x08, # . Report Size (8),
+ 0x95, 0x01, # . Report Count (1),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x03, # . Report ID (3),
+ 0x0A, 0x03, 0x10, # . Usage (1003h),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x26, 0xFF, 0x00, # . Logical Maximum (255),
+ 0x95, 0x01, # . Report Count (1),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x04, # . Report ID (4),
+ 0x0A, 0x04, 0x10, # . Usage (1004h),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0x95, 0x01, # . Report Count (1),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x07, # . Report ID (7),
+ 0x0A, 0x09, 0x10, # . Usage (1009h),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x26, 0xFF, 0x00, # . Logical Maximum (255),
+ 0x95, 0x01, # . Report Count (1),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0xB1, 0x03, # . Feature (Constant, Variable),
+ 0x0A, 0x07, 0x10, # . Usage (1007h),
+ 0x09, 0x00, # . Usage (00h),
+ 0x0A, 0x08, 0x10, # . Usage (1008h),
+ 0x09, 0x00, # . Usage (00h),
+ 0x09, 0x00, # . Usage (00h),
+ 0x09, 0x00, # . Usage (00h),
+ 0x27, 0xFF, 0xFF, 0x00, 0x00, # . Logical Maximum (65535),
+ 0x75, 0x10, # . Report Size (16),
+ 0x95, 0x06, # . Report Count (6),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x09, 0x00, # . Usage (00h),
+ 0x25, 0x00, # . Logical Maximum (0),
+ 0x75, 0x08, # . Report Size (8),
+ 0x95, 0x01, # . Report Count (1),
+ 0xB1, 0x03, # . Feature (Constant, Variable),
+ 0x85, 0x0C, # . Report ID (12),
+ 0x0A, 0x30, 0x0D, # . Usage (0D30h),
+ 0x0A, 0x31, 0x0D, # . Usage (0D31h),
+ 0x0A, 0x32, 0x0D, # . Usage (0D32h),
+ 0x0A, 0x33, 0x0D, # . Usage (0D33h),
+ 0x65, 0x11, # . Unit (Centimeter),
+ 0x55, 0x0D, # . Unit Exponent (13),
+ 0x35, 0x00, # . Physical Minimum (0),
+ 0x46, 0xC8, 0x00, # . Physical Maximum (200),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x26, 0x90, 0x01, # . Logical Maximum (400),
+ 0x75, 0x10, # . Report Size (16),
+ 0x95, 0x04, # . Report Count (4),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x0D, # . Report ID (13),
+ 0x0A, 0x0D, 0x10, # . Usage (100Dh),
+ 0x65, 0x00, # . Unit,
+ 0x55, 0x00, # . Unit Exponent (0),
+ 0x45, 0x00, # . Physical Maximum (0),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0x75, 0x08, # . Report Size (8),
+ 0x95, 0x01, # . Report Count (1),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x14, # . Report ID (20),
+ 0x0A, 0x14, 0x10, # . Usage (1014h),
+ 0x26, 0xFF, 0x00, # . Logical Maximum (255),
+ 0x95, 0x0D, # . Report Count (13),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xCC, # . Report ID (204),
+ 0x0A, 0xCC, 0x10, # . Usage (10CCh),
+ 0x95, 0x02, # . Report Count (2),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0xC0, # . End Collection,
+ 0x09, 0x0E, # . Usage (0Eh),
+ 0xA1, 0x02, # . Collection (Logical),
+ 0x85, 0x31, # . Report ID (49),
+ 0x0A, 0x31, 0x10, # . Usage (1031h),
+ 0x25, 0x64, # . Logical Maximum (100),
+ 0x95, 0x03, # . Report Count (3),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x95, 0x02, # . Report Count (2),
+ 0xB1, 0x03, # . Feature (Constant, Variable),
+ 0xC0, # . End Collection,
+ 0x0A, 0xAC, 0x10, # . Usage (10ACh),
+ 0xA1, 0x02, # . Collection (Logical),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x26, 0xFF, 0x00, # . Logical Maximum (255),
+ 0x75, 0x08, # . Report Size (8),
+ 0x85, 0xAC, # . Report ID (172),
+ 0x09, 0x00, # . Usage (00h),
+ 0x96, 0xBF, 0x00, # . Report Count (191),
+ 0x81, 0x02, # . Input (Variable),
+ 0x85, 0x15, # . Report ID (21),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x0E, # . Report Count (14),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x33, # . Report ID (51),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x12, # . Report Count (18),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x44, # . Report ID (68),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x04, # . Report Count (4),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x45, # . Report ID (69),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x20, # . Report Count (32),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x60, # . Report ID (96),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x3F, # . Report Count (63),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x61, # . Report ID (97),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x3E, # . Report Count (62),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x62, # . Report ID (98),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x3E, # . Report Count (62),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x65, # . Report ID (101),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x04, # . Report Count (4),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x66, # . Report ID (102),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x04, # . Report Count (4),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x67, # . Report ID (103),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x04, # . Report Count (4),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x68, # . Report ID (104),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x11, # . Report Count (17),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x6F, # . Report ID (111),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x3E, # . Report Count (62),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xCD, # . Report ID (205),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x02, # . Report Count (2),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x16, # . Report ID (22),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x0E, # . Report Count (14),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0x35, # . Report ID (53),
+ 0x09, 0x00, # . Usage (00h),
+ 0x95, 0x0A, # . Report Count (10),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0xC0, # . End Collection,
+ 0x85, 0xD1, # . Report ID (209),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x04, 0x01, # . Report Count (260),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xD2, # . Report ID (210),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x04, 0x01, # . Report Count (260),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xD3, # . Report ID (211),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x04, 0x00, # . Report Count (4),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xD4, # . Report ID (212),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x04, 0x00, # . Report Count (4),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xD5, # . Report ID (213),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x04, 0x00, # . Report Count (4),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xD6, # . Report ID (214),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x04, 0x00, # . Report Count (4),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xD7, # . Report ID (215),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x08, 0x00, # . Report Count (8),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xD8, # . Report ID (216),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x0C, 0x00, # . Report Count (12),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xD9, # . Report ID (217),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x00, 0x0A, # . Report Count (2560),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xDA, # . Report ID (218),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x04, 0x04, # . Report Count (1028),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xDB, # . Report ID (219),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x06, 0x00, # . Report Count (6),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xDC, # . Report ID (220),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x02, 0x00, # . Report Count (2),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xDD, # . Report ID (221),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x04, 0x00, # . Report Count (4),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xDE, # . Report ID (222),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x04, 0x00, # . Report Count (4),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xDF, # . Report ID (223),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x22, 0x00, # . Report Count (34),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xE0, # . Report ID (224),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x01, 0x00, # . Report Count (1),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xE1, # . Report ID (225),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x02, 0x00, # . Report Count (2),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xE2, # . Report ID (226),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x02, 0x00, # . Report Count (2),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xE3, # . Report ID (227),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x02, 0x00, # . Report Count (2),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xE4, # . Report ID (228),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0xFF, 0x01, # . Report Count (511),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0x85, 0xCB, # . Report ID (203),
+ 0x09, 0x01, # . Usage (01h),
+ 0x96, 0x1F, 0x00, # . Report Count (31),
+ 0xB1, 0x02, # . Feature (Variable),
+ 0xC0 # . End Collection
+]
+# fmt: on
diff --git a/tools/testing/selftests/hid/tests/test_apple_keyboard.py b/tools/testing/selftests/hid/tests/test_apple_keyboard.py
new file mode 100644
index 000000000000..f81071d46166
--- /dev/null
+++ b/tools/testing/selftests/hid/tests/test_apple_keyboard.py
@@ -0,0 +1,440 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2019 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2019 Red Hat, Inc.
+#
+
+from .test_keyboard import ArrayKeyboard, TestArrayKeyboard
+from hidtools.util import BusType
+
+import libevdev
+import logging
+
+logger = logging.getLogger("hidtools.test.apple-keyboard")
+
+KERNEL_MODULE = ("apple", "hid-apple")
+
+
+class KbdData(object):
+ pass
+
+
+class AppleKeyboard(ArrayKeyboard):
+ # fmt: off
+ report_descriptor = [
+ 0x05, 0x01, # Usage Page (Generic Desktop)
+ 0x09, 0x06, # Usage (Keyboard)
+ 0xa1, 0x01, # Collection (Application)
+ 0x85, 0x01, # .Report ID (1)
+ 0x05, 0x07, # .Usage Page (Keyboard)
+ 0x19, 0xe0, # .Usage Minimum (224)
+ 0x29, 0xe7, # .Usage Maximum (231)
+ 0x15, 0x00, # .Logical Minimum (0)
+ 0x25, 0x01, # .Logical Maximum (1)
+ 0x75, 0x01, # .Report Size (1)
+ 0x95, 0x08, # .Report Count (8)
+ 0x81, 0x02, # .Input (Data,Var,Abs)
+ 0x75, 0x08, # .Report Size (8)
+ 0x95, 0x01, # .Report Count (1)
+ 0x81, 0x01, # .Input (Cnst,Arr,Abs)
+ 0x75, 0x01, # .Report Size (1)
+ 0x95, 0x05, # .Report Count (5)
+ 0x05, 0x08, # .Usage Page (LEDs)
+ 0x19, 0x01, # .Usage Minimum (1)
+ 0x29, 0x05, # .Usage Maximum (5)
+ 0x91, 0x02, # .Output (Data,Var,Abs)
+ 0x75, 0x03, # .Report Size (3)
+ 0x95, 0x01, # .Report Count (1)
+ 0x91, 0x01, # .Output (Cnst,Arr,Abs)
+ 0x75, 0x08, # .Report Size (8)
+ 0x95, 0x06, # .Report Count (6)
+ 0x15, 0x00, # .Logical Minimum (0)
+ 0x26, 0xff, 0x00, # .Logical Maximum (255)
+ 0x05, 0x07, # .Usage Page (Keyboard)
+ 0x19, 0x00, # .Usage Minimum (0)
+ 0x2a, 0xff, 0x00, # .Usage Maximum (255)
+ 0x81, 0x00, # .Input (Data,Arr,Abs)
+ 0xc0, # End Collection
+ 0x05, 0x0c, # Usage Page (Consumer Devices)
+ 0x09, 0x01, # Usage (Consumer Control)
+ 0xa1, 0x01, # Collection (Application)
+ 0x85, 0x47, # .Report ID (71)
+ 0x05, 0x01, # .Usage Page (Generic Desktop)
+ 0x09, 0x06, # .Usage (Keyboard)
+ 0xa1, 0x02, # .Collection (Logical)
+ 0x05, 0x06, # ..Usage Page (Generic Device Controls)
+ 0x09, 0x20, # ..Usage (Battery Strength)
+ 0x15, 0x00, # ..Logical Minimum (0)
+ 0x26, 0xff, 0x00, # ..Logical Maximum (255)
+ 0x75, 0x08, # ..Report Size (8)
+ 0x95, 0x01, # ..Report Count (1)
+ 0x81, 0x02, # ..Input (Data,Var,Abs)
+ 0xc0, # .End Collection
+ 0xc0, # End Collection
+ 0x05, 0x0c, # Usage Page (Consumer Devices)
+ 0x09, 0x01, # Usage (Consumer Control)
+ 0xa1, 0x01, # Collection (Application)
+ 0x85, 0x11, # .Report ID (17)
+ 0x15, 0x00, # .Logical Minimum (0)
+ 0x25, 0x01, # .Logical Maximum (1)
+ 0x75, 0x01, # .Report Size (1)
+ 0x95, 0x03, # .Report Count (3)
+ 0x81, 0x01, # .Input (Cnst,Arr,Abs)
+ 0x75, 0x01, # .Report Size (1)
+ 0x95, 0x01, # .Report Count (1)
+ 0x05, 0x0c, # .Usage Page (Consumer Devices)
+ 0x09, 0xb8, # .Usage (Eject)
+ 0x81, 0x02, # .Input (Data,Var,Abs)
+ 0x06, 0xff, 0x00, # .Usage Page (Vendor Usage Page 0xff)
+ 0x09, 0x03, # .Usage (Vendor Usage 0x03)
+ 0x81, 0x02, # .Input (Data,Var,Abs)
+ 0x75, 0x01, # .Report Size (1)
+ 0x95, 0x03, # .Report Count (3)
+ 0x81, 0x01, # .Input (Cnst,Arr,Abs)
+ 0x05, 0x0c, # .Usage Page (Consumer Devices)
+ 0x85, 0x12, # .Report ID (18)
+ 0x15, 0x00, # .Logical Minimum (0)
+ 0x25, 0x01, # .Logical Maximum (1)
+ 0x75, 0x01, # .Report Size (1)
+ 0x95, 0x01, # .Report Count (1)
+ 0x09, 0xcd, # .Usage (Play/Pause)
+ 0x81, 0x02, # .Input (Data,Var,Abs)
+ 0x09, 0xb3, # .Usage (Fast Forward)
+ 0x81, 0x02, # .Input (Data,Var,Abs)
+ 0x09, 0xb4, # .Usage (Rewind)
+ 0x81, 0x02, # .Input (Data,Var,Abs)
+ 0x09, 0xb5, # .Usage (Scan Next Track)
+ 0x81, 0x02, # .Input (Data,Var,Abs)
+ 0x09, 0xb6, # .Usage (Scan Previous Track)
+ 0x81, 0x02, # .Input (Data,Var,Abs)
+ 0x81, 0x01, # .Input (Cnst,Arr,Abs)
+ 0x81, 0x01, # .Input (Cnst,Arr,Abs)
+ 0x81, 0x01, # .Input (Cnst,Arr,Abs)
+ 0x85, 0x13, # .Report ID (19)
+ 0x15, 0x00, # .Logical Minimum (0)
+ 0x25, 0x01, # .Logical Maximum (1)
+ 0x75, 0x01, # .Report Size (1)
+ 0x95, 0x01, # .Report Count (1)
+ 0x06, 0x01, 0xff, # .Usage Page (Vendor Usage Page 0xff01)
+ 0x09, 0x0a, # .Usage (Vendor Usage 0x0a)
+ 0x81, 0x02, # .Input (Data,Var,Abs)
+ 0x06, 0x01, 0xff, # .Usage Page (Vendor Usage Page 0xff01)
+ 0x09, 0x0c, # .Usage (Vendor Usage 0x0c)
+ 0x81, 0x22, # .Input (Data,Var,Abs,NoPref)
+ 0x75, 0x01, # .Report Size (1)
+ 0x95, 0x06, # .Report Count (6)
+ 0x81, 0x01, # .Input (Cnst,Arr,Abs)
+ 0x85, 0x09, # .Report ID (9)
+ 0x09, 0x0b, # .Usage (Vendor Usage 0x0b)
+ 0x75, 0x08, # .Report Size (8)
+ 0x95, 0x01, # .Report Count (1)
+ 0xb1, 0x02, # .Feature (Data,Var,Abs)
+ 0x75, 0x08, # .Report Size (8)
+ 0x95, 0x02, # .Report Count (2)
+ 0xb1, 0x01, # .Feature (Cnst,Arr,Abs)
+ 0xc0, # End Collection
+ ]
+ # fmt: on
+
+ def __init__(
+ self,
+ rdesc=report_descriptor,
+ name="Apple Wireless Keyboard",
+ input_info=(BusType.BLUETOOTH, 0x05AC, 0x0256),
+ ):
+ super().__init__(rdesc, name, input_info)
+ self.default_reportID = 1
+
+ def send_fn_state(self, state):
+ data = KbdData()
+ setattr(data, "0xff0003", state)
+ r = self.create_report(data, reportID=17)
+ self.call_input_event(r)
+ return [r]
+
+
+class TestAppleKeyboard(TestArrayKeyboard):
+ kernel_modules = [KERNEL_MODULE]
+
+ def create_device(self):
+ return AppleKeyboard()
+
+ def test_single_function_key(self):
+ """check for function key reliability."""
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+ syn_event = self.syn_event
+
+ r = uhdev.event(["F4"])
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_ALL_APPLICATIONS, 1))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+ assert evdev.value[libevdev.EV_KEY.KEY_ALL_APPLICATIONS] == 1
+ assert evdev.value[libevdev.EV_KEY.KEY_FN] == 0
+
+ r = uhdev.event([])
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_ALL_APPLICATIONS, 0))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+ assert evdev.value[libevdev.EV_KEY.KEY_ALL_APPLICATIONS] == 0
+
+ def test_single_fn_function_key(self):
+ """check for function key reliability with the fn key."""
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+ syn_event = self.syn_event
+
+ r = uhdev.send_fn_state(1)
+ r.extend(uhdev.event(["F4"]))
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F4, 1))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 1))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+ assert evdev.value[libevdev.EV_KEY.KEY_F4] == 1
+
+ r = uhdev.event([])
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F4, 0))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+ assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
+ assert evdev.value[libevdev.EV_KEY.KEY_FN] == 1
+
+ r = uhdev.send_fn_state(0)
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 0))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+
+ def test_single_fn_function_key_release_first(self):
+ """check for function key reliability with the fn key."""
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+ syn_event = self.syn_event
+
+ r = uhdev.send_fn_state(1)
+ r.extend(uhdev.event(["F4"]))
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F4, 1))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 1))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+ assert evdev.value[libevdev.EV_KEY.KEY_F4] == 1
+
+ r = uhdev.send_fn_state(0)
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 0))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+
+ r = uhdev.event([])
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F4, 0))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+ assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
+
+ def test_single_fn_function_key_inverted(self):
+ """check for function key reliability with the fn key."""
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+ syn_event = self.syn_event
+
+ r = uhdev.event(["F4"])
+ r.extend(uhdev.send_fn_state(1))
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_ALL_APPLICATIONS, 1))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 1))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+ assert evdev.value[libevdev.EV_KEY.KEY_ALL_APPLICATIONS] == 1
+
+ r = uhdev.event([])
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_ALL_APPLICATIONS, 0))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+ assert evdev.value[libevdev.EV_KEY.KEY_ALL_APPLICATIONS] == 0
+ assert evdev.value[libevdev.EV_KEY.KEY_FN] == 1
+
+ r = uhdev.send_fn_state(0)
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 0))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+
+ def test_multiple_fn_function_key_release_first(self):
+ """check for function key reliability with the fn key."""
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+ syn_event = self.syn_event
+
+ r = uhdev.send_fn_state(1)
+ r.extend(uhdev.event(["F4"]))
+ r.extend(uhdev.event(["F4", "F6"]))
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F4, 1))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F6, 1))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 1))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+ assert evdev.value[libevdev.EV_KEY.KEY_F4] == 1
+ assert evdev.value[libevdev.EV_KEY.KEY_F6] == 1
+ assert evdev.value[libevdev.EV_KEY.KEY_FN] == 1
+
+ r = uhdev.event(["F6"])
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F4, 0))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+ assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
+ assert evdev.value[libevdev.EV_KEY.KEY_F6] == 1
+ assert evdev.value[libevdev.EV_KEY.KEY_FN] == 1
+
+ r = uhdev.send_fn_state(0)
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 0))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+ assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
+ assert evdev.value[libevdev.EV_KEY.KEY_F6] == 1
+ assert evdev.value[libevdev.EV_KEY.KEY_FN] == 0
+
+ r = uhdev.event([])
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F6, 0))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+ assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
+ assert evdev.value[libevdev.EV_KEY.KEY_F6] == 0
+ assert evdev.value[libevdev.EV_KEY.KEY_FN] == 0
+
+ def test_multiple_fn_function_key_release_between(self):
+ """check for function key reliability with the fn key."""
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+ syn_event = self.syn_event
+
+ # press F4
+ r = uhdev.event(["F4"])
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_ALL_APPLICATIONS, 1))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+ assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
+ assert evdev.value[libevdev.EV_KEY.KEY_ALL_APPLICATIONS] == 1
+ assert evdev.value[libevdev.EV_KEY.KEY_F6] == 0
+ assert evdev.value[libevdev.EV_KEY.KEY_KBDILLUMUP] == 0
+ assert evdev.value[libevdev.EV_KEY.KEY_FN] == 0
+
+ # press Fn key
+ r = uhdev.send_fn_state(1)
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 1))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+ assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
+ assert evdev.value[libevdev.EV_KEY.KEY_ALL_APPLICATIONS] == 1
+ assert evdev.value[libevdev.EV_KEY.KEY_F6] == 0
+ assert evdev.value[libevdev.EV_KEY.KEY_KBDILLUMUP] == 0
+ assert evdev.value[libevdev.EV_KEY.KEY_FN] == 1
+
+ # keep F4 and press F6
+ r = uhdev.event(["F4", "F6"])
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F6, 1))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+ assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
+ assert evdev.value[libevdev.EV_KEY.KEY_ALL_APPLICATIONS] == 1
+ assert evdev.value[libevdev.EV_KEY.KEY_F6] == 1
+ assert evdev.value[libevdev.EV_KEY.KEY_KBDILLUMUP] == 0
+ assert evdev.value[libevdev.EV_KEY.KEY_FN] == 1
+
+ # keep F4 and F6
+ r = uhdev.event(["F4", "F6"])
+ expected = []
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+ assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
+ assert evdev.value[libevdev.EV_KEY.KEY_ALL_APPLICATIONS] == 1
+ assert evdev.value[libevdev.EV_KEY.KEY_F6] == 1
+ assert evdev.value[libevdev.EV_KEY.KEY_KBDILLUMUP] == 0
+ assert evdev.value[libevdev.EV_KEY.KEY_FN] == 1
+
+ # release Fn key and all keys
+ r = uhdev.send_fn_state(0)
+ r.extend(uhdev.event([]))
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_ALL_APPLICATIONS, 0))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_F6, 0))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+ assert evdev.value[libevdev.EV_KEY.KEY_F4] == 0
+ assert evdev.value[libevdev.EV_KEY.KEY_ALL_APPLICATIONS] == 0
+ assert evdev.value[libevdev.EV_KEY.KEY_F6] == 0
+ assert evdev.value[libevdev.EV_KEY.KEY_KBDILLUMUP] == 0
+ assert evdev.value[libevdev.EV_KEY.KEY_FN] == 0
+
+ def test_single_pageup_key_release_first(self):
+ """check for function key reliability with the [page] up key."""
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+ syn_event = self.syn_event
+
+ r = uhdev.send_fn_state(1)
+ r.extend(uhdev.event(["UpArrow"]))
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_PAGEUP, 1))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 1))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+ assert evdev.value[libevdev.EV_KEY.KEY_PAGEUP] == 1
+ assert evdev.value[libevdev.EV_KEY.KEY_UP] == 0
+ assert evdev.value[libevdev.EV_KEY.KEY_FN] == 1
+
+ r = uhdev.send_fn_state(0)
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_FN, 0))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+ assert evdev.value[libevdev.EV_KEY.KEY_PAGEUP] == 1
+ assert evdev.value[libevdev.EV_KEY.KEY_UP] == 0
+ assert evdev.value[libevdev.EV_KEY.KEY_FN] == 0
+
+ r = uhdev.event([])
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_PAGEUP, 0))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+ assert evdev.value[libevdev.EV_KEY.KEY_PAGEUP] == 0
+ assert evdev.value[libevdev.EV_KEY.KEY_UP] == 0
+ assert evdev.value[libevdev.EV_KEY.KEY_FN] == 0
diff --git a/tools/testing/selftests/hid/tests/test_gamepad.py b/tools/testing/selftests/hid/tests/test_gamepad.py
new file mode 100644
index 000000000000..8d5b5ffdae49
--- /dev/null
+++ b/tools/testing/selftests/hid/tests/test_gamepad.py
@@ -0,0 +1,664 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2019 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2019 Red Hat, Inc.
+#
+
+from . import base
+import libevdev
+import pytest
+
+from .base_gamepad import BaseGamepad, JoystickGamepad, AxisMapping
+from hidtools.util import BusType
+
+import logging
+
+logger = logging.getLogger("hidtools.test.gamepad")
+
+
+class BaseTest:
+ class TestGamepad(base.BaseTestCase.TestUhid):
+ @pytest.fixture(autouse=True)
+ def send_initial_state(self):
+ """send an empty report to initialize the axes"""
+ uhdev = self.uhdev
+
+ r = uhdev.event()
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+
+ def assert_button(self, button):
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+ syn_event = self.syn_event
+
+ buttons = {}
+ key = libevdev.evbit(uhdev.buttons_map[button])
+
+ buttons[button] = True
+ r = uhdev.event(buttons=buttons)
+ expected_event = libevdev.InputEvent(key, 1)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn((syn_event, expected_event), events)
+ assert evdev.value[key] == 1
+
+ buttons[button] = False
+ r = uhdev.event(buttons=buttons)
+ expected_event = libevdev.InputEvent(key, 0)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn((syn_event, expected_event), events)
+ assert evdev.value[key] == 0
+
+ def test_buttons(self):
+ """check for button reliability."""
+ uhdev = self.uhdev
+
+ for b in uhdev.buttons:
+ self.assert_button(b)
+
+ def test_dual_buttons(self):
+ """check for button reliability when pressing 2 buttons"""
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+ syn_event = self.syn_event
+
+ # can change intended b1 b2 values
+ b1 = uhdev.buttons[0]
+ key1 = libevdev.evbit(uhdev.buttons_map[b1])
+ b2 = uhdev.buttons[1]
+ key2 = libevdev.evbit(uhdev.buttons_map[b2])
+
+ buttons = {b1: True, b2: True}
+ r = uhdev.event(buttons=buttons)
+ expected_event0 = libevdev.InputEvent(key1, 1)
+ expected_event1 = libevdev.InputEvent(key2, 1)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(
+ (syn_event, expected_event0, expected_event1), events
+ )
+ assert evdev.value[key1] == 1
+ assert evdev.value[key2] == 1
+
+ buttons = {b1: False, b2: None}
+ r = uhdev.event(buttons=buttons)
+ expected_event = libevdev.InputEvent(key1, 0)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn((syn_event, expected_event), events)
+ assert evdev.value[key1] == 0
+ assert evdev.value[key2] == 1
+
+ buttons = {b1: None, b2: False}
+ r = uhdev.event(buttons=buttons)
+ expected_event = libevdev.InputEvent(key2, 0)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn((syn_event, expected_event), events)
+ assert evdev.value[key1] == 0
+ assert evdev.value[key2] == 0
+
+ def _get_libevdev_abs_events(self, which):
+ """Returns which ABS_* evdev axes are expected for the given stick"""
+ abs_map = self.uhdev.axes_map[which]
+
+ x = abs_map["x"].evdev
+ y = abs_map["y"].evdev
+
+ assert x
+ assert y
+
+ return x, y
+
+ def _test_joystick_press(self, which, data):
+ uhdev = self.uhdev
+
+ libevdev_axes = self._get_libevdev_abs_events(which)
+
+ r = None
+ if which == "left_stick":
+ r = uhdev.event(left=data)
+ else:
+ r = uhdev.event(right=data)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+
+ for i, d in enumerate(data):
+ if d is not None and d != 127:
+ assert libevdev.InputEvent(libevdev_axes[i], d) in events
+ else:
+ assert libevdev.InputEvent(libevdev_axes[i]) not in events
+
+ def test_left_joystick_press_left(self):
+ """check for the left joystick reliability"""
+ self._test_joystick_press("left_stick", (63, None))
+ self._test_joystick_press("left_stick", (0, 127))
+
+ def test_left_joystick_press_right(self):
+ """check for the left joystick reliability"""
+ self._test_joystick_press("left_stick", (191, 127))
+ self._test_joystick_press("left_stick", (255, None))
+
+ def test_left_joystick_press_up(self):
+ """check for the left joystick reliability"""
+ self._test_joystick_press("left_stick", (None, 63))
+ self._test_joystick_press("left_stick", (127, 0))
+
+ def test_left_joystick_press_down(self):
+ """check for the left joystick reliability"""
+ self._test_joystick_press("left_stick", (127, 191))
+ self._test_joystick_press("left_stick", (None, 255))
+
+ def test_right_joystick_press_left(self):
+ """check for the right joystick reliability"""
+ self._test_joystick_press("right_stick", (63, None))
+ self._test_joystick_press("right_stick", (0, 127))
+
+ def test_right_joystick_press_right(self):
+ """check for the right joystick reliability"""
+ self._test_joystick_press("right_stick", (191, 127))
+ self._test_joystick_press("right_stick", (255, None))
+
+ def test_right_joystick_press_up(self):
+ """check for the right joystick reliability"""
+ self._test_joystick_press("right_stick", (None, 63))
+ self._test_joystick_press("right_stick", (127, 0))
+
+ def test_right_joystick_press_down(self):
+ """check for the right joystick reliability"""
+ self._test_joystick_press("right_stick", (127, 191))
+ self._test_joystick_press("right_stick", (None, 255))
+
+ @pytest.mark.skip_if_uhdev(
+ lambda uhdev: "Hat switch" not in uhdev.fields,
+ "Device not compatible, missing Hat switch usage",
+ )
+ @pytest.mark.parametrize(
+ "hat_value,expected_evdev,evdev_value",
+ [
+ (0, "ABS_HAT0Y", -1),
+ (2, "ABS_HAT0X", 1),
+ (4, "ABS_HAT0Y", 1),
+ (6, "ABS_HAT0X", -1),
+ ],
+ )
+ def test_hat_switch(self, hat_value, expected_evdev, evdev_value):
+ uhdev = self.uhdev
+
+ r = uhdev.event(hat_switch=hat_value)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ assert (
+ libevdev.InputEvent(
+ libevdev.evbit("EV_ABS", expected_evdev), evdev_value
+ )
+ in events
+ )
+
+
+class SaitekGamepad(JoystickGamepad):
+ # fmt: off
+ report_descriptor = [
+ 0x05, 0x01, # Usage Page (Generic Desktop) 0
+ 0x09, 0x04, # Usage (Joystick) 2
+ 0xa1, 0x01, # Collection (Application) 4
+ 0x09, 0x01, # .Usage (Pointer) 6
+ 0xa1, 0x00, # .Collection (Physical) 8
+ 0x85, 0x01, # ..Report ID (1) 10
+ 0x09, 0x30, # ..Usage (X) 12
+ 0x15, 0x00, # ..Logical Minimum (0) 14
+ 0x26, 0xff, 0x00, # ..Logical Maximum (255) 16
+ 0x35, 0x00, # ..Physical Minimum (0) 19
+ 0x46, 0xff, 0x00, # ..Physical Maximum (255) 21
+ 0x75, 0x08, # ..Report Size (8) 24
+ 0x95, 0x01, # ..Report Count (1) 26
+ 0x81, 0x02, # ..Input (Data,Var,Abs) 28
+ 0x09, 0x31, # ..Usage (Y) 30
+ 0x81, 0x02, # ..Input (Data,Var,Abs) 32
+ 0x05, 0x02, # ..Usage Page (Simulation Controls) 34
+ 0x09, 0xba, # ..Usage (Rudder) 36
+ 0x81, 0x02, # ..Input (Data,Var,Abs) 38
+ 0x09, 0xbb, # ..Usage (Throttle) 40
+ 0x81, 0x02, # ..Input (Data,Var,Abs) 42
+ 0x05, 0x09, # ..Usage Page (Button) 44
+ 0x19, 0x01, # ..Usage Minimum (1) 46
+ 0x29, 0x0c, # ..Usage Maximum (12) 48
+ 0x25, 0x01, # ..Logical Maximum (1) 50
+ 0x45, 0x01, # ..Physical Maximum (1) 52
+ 0x75, 0x01, # ..Report Size (1) 54
+ 0x95, 0x0c, # ..Report Count (12) 56
+ 0x81, 0x02, # ..Input (Data,Var,Abs) 58
+ 0x95, 0x01, # ..Report Count (1) 60
+ 0x75, 0x00, # ..Report Size (0) 62
+ 0x81, 0x03, # ..Input (Cnst,Var,Abs) 64
+ 0x05, 0x01, # ..Usage Page (Generic Desktop) 66
+ 0x09, 0x39, # ..Usage (Hat switch) 68
+ 0x25, 0x07, # ..Logical Maximum (7) 70
+ 0x46, 0x3b, 0x01, # ..Physical Maximum (315) 72
+ 0x55, 0x00, # ..Unit Exponent (0) 75
+ 0x65, 0x44, # ..Unit (Degrees^4,EngRotation) 77
+ 0x75, 0x04, # ..Report Size (4) 79
+ 0x81, 0x42, # ..Input (Data,Var,Abs,Null) 81
+ 0x65, 0x00, # ..Unit (None) 83
+ 0xc0, # .End Collection 85
+ 0x05, 0x0f, # .Usage Page (Vendor Usage Page 0x0f) 86
+ 0x09, 0x92, # .Usage (Vendor Usage 0x92) 88
+ 0xa1, 0x02, # .Collection (Logical) 90
+ 0x85, 0x02, # ..Report ID (2) 92
+ 0x09, 0xa0, # ..Usage (Vendor Usage 0xa0) 94
+ 0x09, 0x9f, # ..Usage (Vendor Usage 0x9f) 96
+ 0x25, 0x01, # ..Logical Maximum (1) 98
+ 0x45, 0x00, # ..Physical Maximum (0) 100
+ 0x75, 0x01, # ..Report Size (1) 102
+ 0x95, 0x02, # ..Report Count (2) 104
+ 0x81, 0x02, # ..Input (Data,Var,Abs) 106
+ 0x75, 0x06, # ..Report Size (6) 108
+ 0x95, 0x01, # ..Report Count (1) 110
+ 0x81, 0x03, # ..Input (Cnst,Var,Abs) 112
+ 0x09, 0x22, # ..Usage (Vendor Usage 0x22) 114
+ 0x75, 0x07, # ..Report Size (7) 116
+ 0x25, 0x7f, # ..Logical Maximum (127) 118
+ 0x81, 0x02, # ..Input (Data,Var,Abs) 120
+ 0x09, 0x94, # ..Usage (Vendor Usage 0x94) 122
+ 0x75, 0x01, # ..Report Size (1) 124
+ 0x25, 0x01, # ..Logical Maximum (1) 126
+ 0x81, 0x02, # ..Input (Data,Var,Abs) 128
+ 0xc0, # .End Collection 130
+ 0x09, 0x21, # .Usage (Vendor Usage 0x21) 131
+ 0xa1, 0x02, # .Collection (Logical) 133
+ 0x85, 0x0b, # ..Report ID (11) 135
+ 0x09, 0x22, # ..Usage (Vendor Usage 0x22) 137
+ 0x26, 0xff, 0x00, # ..Logical Maximum (255) 139
+ 0x75, 0x08, # ..Report Size (8) 142
+ 0x91, 0x02, # ..Output (Data,Var,Abs) 144
+ 0x09, 0x53, # ..Usage (Vendor Usage 0x53) 146
+ 0x25, 0x0a, # ..Logical Maximum (10) 148
+ 0x91, 0x02, # ..Output (Data,Var,Abs) 150
+ 0x09, 0x50, # ..Usage (Vendor Usage 0x50) 152
+ 0x27, 0xfe, 0xff, 0x00, 0x00, # ..Logical Maximum (65534) 154
+ 0x47, 0xfe, 0xff, 0x00, 0x00, # ..Physical Maximum (65534) 159
+ 0x75, 0x10, # ..Report Size (16) 164
+ 0x55, 0xfd, # ..Unit Exponent (237) 166
+ 0x66, 0x01, 0x10, # ..Unit (Seconds,SILinear) 168
+ 0x91, 0x02, # ..Output (Data,Var,Abs) 171
+ 0x55, 0x00, # ..Unit Exponent (0) 173
+ 0x65, 0x00, # ..Unit (None) 175
+ 0x09, 0x54, # ..Usage (Vendor Usage 0x54) 177
+ 0x55, 0xfd, # ..Unit Exponent (237) 179
+ 0x66, 0x01, 0x10, # ..Unit (Seconds,SILinear) 181
+ 0x91, 0x02, # ..Output (Data,Var,Abs) 184
+ 0x55, 0x00, # ..Unit Exponent (0) 186
+ 0x65, 0x00, # ..Unit (None) 188
+ 0x09, 0xa7, # ..Usage (Vendor Usage 0xa7) 190
+ 0x55, 0xfd, # ..Unit Exponent (237) 192
+ 0x66, 0x01, 0x10, # ..Unit (Seconds,SILinear) 194
+ 0x91, 0x02, # ..Output (Data,Var,Abs) 197
+ 0x55, 0x00, # ..Unit Exponent (0) 199
+ 0x65, 0x00, # ..Unit (None) 201
+ 0xc0, # .End Collection 203
+ 0x09, 0x5a, # .Usage (Vendor Usage 0x5a) 204
+ 0xa1, 0x02, # .Collection (Logical) 206
+ 0x85, 0x0c, # ..Report ID (12) 208
+ 0x09, 0x22, # ..Usage (Vendor Usage 0x22) 210
+ 0x26, 0xff, 0x00, # ..Logical Maximum (255) 212
+ 0x45, 0x00, # ..Physical Maximum (0) 215
+ 0x75, 0x08, # ..Report Size (8) 217
+ 0x91, 0x02, # ..Output (Data,Var,Abs) 219
+ 0x09, 0x5c, # ..Usage (Vendor Usage 0x5c) 221
+ 0x26, 0x10, 0x27, # ..Logical Maximum (10000) 223
+ 0x46, 0x10, 0x27, # ..Physical Maximum (10000) 226
+ 0x75, 0x10, # ..Report Size (16) 229
+ 0x55, 0xfd, # ..Unit Exponent (237) 231
+ 0x66, 0x01, 0x10, # ..Unit (Seconds,SILinear) 233
+ 0x91, 0x02, # ..Output (Data,Var,Abs) 236
+ 0x55, 0x00, # ..Unit Exponent (0) 238
+ 0x65, 0x00, # ..Unit (None) 240
+ 0x09, 0x5b, # ..Usage (Vendor Usage 0x5b) 242
+ 0x25, 0x7f, # ..Logical Maximum (127) 244
+ 0x75, 0x08, # ..Report Size (8) 246
+ 0x91, 0x02, # ..Output (Data,Var,Abs) 248
+ 0x09, 0x5e, # ..Usage (Vendor Usage 0x5e) 250
+ 0x26, 0x10, 0x27, # ..Logical Maximum (10000) 252
+ 0x75, 0x10, # ..Report Size (16) 255
+ 0x55, 0xfd, # ..Unit Exponent (237) 257
+ 0x66, 0x01, 0x10, # ..Unit (Seconds,SILinear) 259
+ 0x91, 0x02, # ..Output (Data,Var,Abs) 262
+ 0x55, 0x00, # ..Unit Exponent (0) 264
+ 0x65, 0x00, # ..Unit (None) 266
+ 0x09, 0x5d, # ..Usage (Vendor Usage 0x5d) 268
+ 0x25, 0x7f, # ..Logical Maximum (127) 270
+ 0x75, 0x08, # ..Report Size (8) 272
+ 0x91, 0x02, # ..Output (Data,Var,Abs) 274
+ 0xc0, # .End Collection 276
+ 0x09, 0x73, # .Usage (Vendor Usage 0x73) 277
+ 0xa1, 0x02, # .Collection (Logical) 279
+ 0x85, 0x0d, # ..Report ID (13) 281
+ 0x09, 0x22, # ..Usage (Vendor Usage 0x22) 283
+ 0x26, 0xff, 0x00, # ..Logical Maximum (255) 285
+ 0x45, 0x00, # ..Physical Maximum (0) 288
+ 0x91, 0x02, # ..Output (Data,Var,Abs) 290
+ 0x09, 0x70, # ..Usage (Vendor Usage 0x70) 292
+ 0x15, 0x81, # ..Logical Minimum (-127) 294
+ 0x25, 0x7f, # ..Logical Maximum (127) 296
+ 0x36, 0xf0, 0xd8, # ..Physical Minimum (-10000) 298
+ 0x46, 0x10, 0x27, # ..Physical Maximum (10000) 301
+ 0x91, 0x02, # ..Output (Data,Var,Abs) 304
+ 0xc0, # .End Collection 306
+ 0x09, 0x6e, # .Usage (Vendor Usage 0x6e) 307
+ 0xa1, 0x02, # .Collection (Logical) 309
+ 0x85, 0x0e, # ..Report ID (14) 311
+ 0x09, 0x22, # ..Usage (Vendor Usage 0x22) 313
+ 0x15, 0x00, # ..Logical Minimum (0) 315
+ 0x26, 0xff, 0x00, # ..Logical Maximum (255) 317
+ 0x35, 0x00, # ..Physical Minimum (0) 320
+ 0x45, 0x00, # ..Physical Maximum (0) 322
+ 0x91, 0x02, # ..Output (Data,Var,Abs) 324
+ 0x09, 0x70, # ..Usage (Vendor Usage 0x70) 326
+ 0x25, 0x7f, # ..Logical Maximum (127) 328
+ 0x46, 0x10, 0x27, # ..Physical Maximum (10000) 330
+ 0x91, 0x02, # ..Output (Data,Var,Abs) 333
+ 0x09, 0x6f, # ..Usage (Vendor Usage 0x6f) 335
+ 0x15, 0x81, # ..Logical Minimum (-127) 337
+ 0x36, 0xf0, 0xd8, # ..Physical Minimum (-10000) 339
+ 0x91, 0x02, # ..Output (Data,Var,Abs) 342
+ 0x09, 0x71, # ..Usage (Vendor Usage 0x71) 344
+ 0x15, 0x00, # ..Logical Minimum (0) 346
+ 0x26, 0xff, 0x00, # ..Logical Maximum (255) 348
+ 0x35, 0x00, # ..Physical Minimum (0) 351
+ 0x46, 0x68, 0x01, # ..Physical Maximum (360) 353
+ 0x91, 0x02, # ..Output (Data,Var,Abs) 356
+ 0x09, 0x72, # ..Usage (Vendor Usage 0x72) 358
+ 0x75, 0x10, # ..Report Size (16) 360
+ 0x26, 0x10, 0x27, # ..Logical Maximum (10000) 362
+ 0x46, 0x10, 0x27, # ..Physical Maximum (10000) 365
+ 0x55, 0xfd, # ..Unit Exponent (237) 368
+ 0x66, 0x01, 0x10, # ..Unit (Seconds,SILinear) 370
+ 0x91, 0x02, # ..Output (Data,Var,Abs) 373
+ 0x55, 0x00, # ..Unit Exponent (0) 375
+ 0x65, 0x00, # ..Unit (None) 377
+ 0xc0, # .End Collection 379
+ 0x09, 0x77, # .Usage (Vendor Usage 0x77) 380
+ 0xa1, 0x02, # .Collection (Logical) 382
+ 0x85, 0x51, # ..Report ID (81) 384
+ 0x09, 0x22, # ..Usage (Vendor Usage 0x22) 386
+ 0x25, 0x7f, # ..Logical Maximum (127) 388
+ 0x45, 0x00, # ..Physical Maximum (0) 390
+ 0x75, 0x08, # ..Report Size (8) 392
+ 0x91, 0x02, # ..Output (Data,Var,Abs) 394
+ 0x09, 0x78, # ..Usage (Vendor Usage 0x78) 396
+ 0xa1, 0x02, # ..Collection (Logical) 398
+ 0x09, 0x7b, # ...Usage (Vendor Usage 0x7b) 400
+ 0x09, 0x79, # ...Usage (Vendor Usage 0x79) 402
+ 0x09, 0x7a, # ...Usage (Vendor Usage 0x7a) 404
+ 0x15, 0x01, # ...Logical Minimum (1) 406
+ 0x25, 0x03, # ...Logical Maximum (3) 408
+ 0x91, 0x00, # ...Output (Data,Arr,Abs) 410
+ 0xc0, # ..End Collection 412
+ 0x09, 0x7c, # ..Usage (Vendor Usage 0x7c) 413
+ 0x15, 0x00, # ..Logical Minimum (0) 415
+ 0x26, 0xfe, 0x00, # ..Logical Maximum (254) 417
+ 0x91, 0x02, # ..Output (Data,Var,Abs) 420
+ 0xc0, # .End Collection 422
+ 0x09, 0x92, # .Usage (Vendor Usage 0x92) 423
+ 0xa1, 0x02, # .Collection (Logical) 425
+ 0x85, 0x52, # ..Report ID (82) 427
+ 0x09, 0x96, # ..Usage (Vendor Usage 0x96) 429
+ 0xa1, 0x02, # ..Collection (Logical) 431
+ 0x09, 0x9a, # ...Usage (Vendor Usage 0x9a) 433
+ 0x09, 0x99, # ...Usage (Vendor Usage 0x99) 435
+ 0x09, 0x97, # ...Usage (Vendor Usage 0x97) 437
+ 0x09, 0x98, # ...Usage (Vendor Usage 0x98) 439
+ 0x09, 0x9b, # ...Usage (Vendor Usage 0x9b) 441
+ 0x09, 0x9c, # ...Usage (Vendor Usage 0x9c) 443
+ 0x15, 0x01, # ...Logical Minimum (1) 445
+ 0x25, 0x06, # ...Logical Maximum (6) 447
+ 0x91, 0x00, # ...Output (Data,Arr,Abs) 449
+ 0xc0, # ..End Collection 451
+ 0xc0, # .End Collection 452
+ 0x05, 0xff, # .Usage Page (Vendor Usage Page 0xff) 453
+ 0x0a, 0x01, 0x03, # .Usage (Vendor Usage 0x301) 455
+ 0xa1, 0x02, # .Collection (Logical) 458
+ 0x85, 0x40, # ..Report ID (64) 460
+ 0x0a, 0x02, 0x03, # ..Usage (Vendor Usage 0x302) 462
+ 0xa1, 0x02, # ..Collection (Logical) 465
+ 0x1a, 0x11, 0x03, # ...Usage Minimum (785) 467
+ 0x2a, 0x20, 0x03, # ...Usage Maximum (800) 470
+ 0x25, 0x10, # ...Logical Maximum (16) 473
+ 0x91, 0x00, # ...Output (Data,Arr,Abs) 475
+ 0xc0, # ..End Collection 477
+ 0x0a, 0x03, 0x03, # ..Usage (Vendor Usage 0x303) 478
+ 0x15, 0x00, # ..Logical Minimum (0) 481
+ 0x27, 0xff, 0xff, 0x00, 0x00, # ..Logical Maximum (65535) 483
+ 0x75, 0x10, # ..Report Size (16) 488
+ 0x91, 0x02, # ..Output (Data,Var,Abs) 490
+ 0xc0, # .End Collection 492
+ 0x05, 0x0f, # .Usage Page (Vendor Usage Page 0x0f) 493
+ 0x09, 0x7d, # .Usage (Vendor Usage 0x7d) 495
+ 0xa1, 0x02, # .Collection (Logical) 497
+ 0x85, 0x43, # ..Report ID (67) 499
+ 0x09, 0x7e, # ..Usage (Vendor Usage 0x7e) 501
+ 0x26, 0x80, 0x00, # ..Logical Maximum (128) 503
+ 0x46, 0x10, 0x27, # ..Physical Maximum (10000) 506
+ 0x75, 0x08, # ..Report Size (8) 509
+ 0x91, 0x02, # ..Output (Data,Var,Abs) 511
+ 0xc0, # .End Collection 513
+ 0x09, 0x7f, # .Usage (Vendor Usage 0x7f) 514
+ 0xa1, 0x02, # .Collection (Logical) 516
+ 0x85, 0x0b, # ..Report ID (11) 518
+ 0x09, 0x80, # ..Usage (Vendor Usage 0x80) 520
+ 0x26, 0xff, 0x7f, # ..Logical Maximum (32767) 522
+ 0x45, 0x00, # ..Physical Maximum (0) 525
+ 0x75, 0x0f, # ..Report Size (15) 527
+ 0xb1, 0x03, # ..Feature (Cnst,Var,Abs) 529
+ 0x09, 0xa9, # ..Usage (Vendor Usage 0xa9) 531
+ 0x25, 0x01, # ..Logical Maximum (1) 533
+ 0x75, 0x01, # ..Report Size (1) 535
+ 0xb1, 0x03, # ..Feature (Cnst,Var,Abs) 537
+ 0x09, 0x83, # ..Usage (Vendor Usage 0x83) 539
+ 0x26, 0xff, 0x00, # ..Logical Maximum (255) 541
+ 0x75, 0x08, # ..Report Size (8) 544
+ 0xb1, 0x03, # ..Feature (Cnst,Var,Abs) 546
+ 0xc0, # .End Collection 548
+ 0x09, 0xab, # .Usage (Vendor Usage 0xab) 549
+ 0xa1, 0x03, # .Collection (Report) 551
+ 0x85, 0x15, # ..Report ID (21) 553
+ 0x09, 0x25, # ..Usage (Vendor Usage 0x25) 555
+ 0xa1, 0x02, # ..Collection (Logical) 557
+ 0x09, 0x26, # ...Usage (Vendor Usage 0x26) 559
+ 0x09, 0x30, # ...Usage (Vendor Usage 0x30) 561
+ 0x09, 0x32, # ...Usage (Vendor Usage 0x32) 563
+ 0x09, 0x31, # ...Usage (Vendor Usage 0x31) 565
+ 0x09, 0x33, # ...Usage (Vendor Usage 0x33) 567
+ 0x09, 0x34, # ...Usage (Vendor Usage 0x34) 569
+ 0x15, 0x01, # ...Logical Minimum (1) 571
+ 0x25, 0x06, # ...Logical Maximum (6) 573
+ 0xb1, 0x00, # ...Feature (Data,Arr,Abs) 575
+ 0xc0, # ..End Collection 577
+ 0xc0, # .End Collection 578
+ 0x09, 0x89, # .Usage (Vendor Usage 0x89) 579
+ 0xa1, 0x03, # .Collection (Report) 581
+ 0x85, 0x16, # ..Report ID (22) 583
+ 0x09, 0x8b, # ..Usage (Vendor Usage 0x8b) 585
+ 0xa1, 0x02, # ..Collection (Logical) 587
+ 0x09, 0x8c, # ...Usage (Vendor Usage 0x8c) 589
+ 0x09, 0x8d, # ...Usage (Vendor Usage 0x8d) 591
+ 0x09, 0x8e, # ...Usage (Vendor Usage 0x8e) 593
+ 0x25, 0x03, # ...Logical Maximum (3) 595
+ 0xb1, 0x00, # ...Feature (Data,Arr,Abs) 597
+ 0xc0, # ..End Collection 599
+ 0x09, 0x22, # ..Usage (Vendor Usage 0x22) 600
+ 0x15, 0x00, # ..Logical Minimum (0) 602
+ 0x26, 0xfe, 0x00, # ..Logical Maximum (254) 604
+ 0xb1, 0x02, # ..Feature (Data,Var,Abs) 607
+ 0xc0, # .End Collection 609
+ 0x09, 0x90, # .Usage (Vendor Usage 0x90) 610
+ 0xa1, 0x03, # .Collection (Report) 612
+ 0x85, 0x50, # ..Report ID (80) 614
+ 0x09, 0x22, # ..Usage (Vendor Usage 0x22) 616
+ 0x26, 0xff, 0x00, # ..Logical Maximum (255) 618
+ 0x91, 0x02, # ..Output (Data,Var,Abs) 621
+ 0xc0, # .End Collection 623
+ 0xc0, # End Collection 624
+ ]
+ # fmt: on
+
+ def __init__(self, rdesc=report_descriptor, name=None):
+ super().__init__(rdesc, name=name, input_info=(BusType.USB, 0x06A3, 0xFF0D))
+ self.buttons = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
+
+
+class AsusGamepad(BaseGamepad):
+ # fmt: off
+ report_descriptor = [
+ 0x05, 0x01, # Usage Page (Generic Desktop) 0
+ 0x09, 0x05, # Usage (Game Pad) 2
+ 0xa1, 0x01, # Collection (Application) 4
+ 0x85, 0x01, # .Report ID (1) 6
+ 0x05, 0x09, # .Usage Page (Button) 8
+ 0x0a, 0x01, 0x00, # .Usage (Vendor Usage 0x01) 10
+ 0x0a, 0x02, 0x00, # .Usage (Vendor Usage 0x02) 13
+ 0x0a, 0x04, 0x00, # .Usage (Vendor Usage 0x04) 16
+ 0x0a, 0x05, 0x00, # .Usage (Vendor Usage 0x05) 19
+ 0x0a, 0x07, 0x00, # .Usage (Vendor Usage 0x07) 22
+ 0x0a, 0x08, 0x00, # .Usage (Vendor Usage 0x08) 25
+ 0x0a, 0x0e, 0x00, # .Usage (Vendor Usage 0x0e) 28
+ 0x0a, 0x0f, 0x00, # .Usage (Vendor Usage 0x0f) 31
+ 0x0a, 0x0d, 0x00, # .Usage (Vendor Usage 0x0d) 34
+ 0x05, 0x0c, # .Usage Page (Consumer Devices) 37
+ 0x0a, 0x24, 0x02, # .Usage (AC Back) 39
+ 0x0a, 0x23, 0x02, # .Usage (AC Home) 42
+ 0x15, 0x00, # .Logical Minimum (0) 45
+ 0x25, 0x01, # .Logical Maximum (1) 47
+ 0x75, 0x01, # .Report Size (1) 49
+ 0x95, 0x0b, # .Report Count (11) 51
+ 0x81, 0x02, # .Input (Data,Var,Abs) 53
+ 0x75, 0x01, # .Report Size (1) 55
+ 0x95, 0x01, # .Report Count (1) 57
+ 0x81, 0x03, # .Input (Cnst,Var,Abs) 59
+ 0x05, 0x01, # .Usage Page (Generic Desktop) 61
+ 0x75, 0x04, # .Report Size (4) 63
+ 0x95, 0x01, # .Report Count (1) 65
+ 0x25, 0x07, # .Logical Maximum (7) 67
+ 0x46, 0x3b, 0x01, # .Physical Maximum (315) 69
+ 0x66, 0x14, 0x00, # .Unit (Degrees,EngRotation) 72
+ 0x09, 0x39, # .Usage (Hat switch) 75
+ 0x81, 0x42, # .Input (Data,Var,Abs,Null) 77
+ 0x66, 0x00, 0x00, # .Unit (None) 79
+ 0x09, 0x01, # .Usage (Pointer) 82
+ 0xa1, 0x00, # .Collection (Physical) 84
+ 0x09, 0x30, # ..Usage (X) 86
+ 0x09, 0x31, # ..Usage (Y) 88
+ 0x09, 0x32, # ..Usage (Z) 90
+ 0x09, 0x35, # ..Usage (Rz) 92
+ 0x05, 0x02, # ..Usage Page (Simulation Controls) 94
+ 0x09, 0xc5, # ..Usage (Brake) 96
+ 0x09, 0xc4, # ..Usage (Accelerator) 98
+ 0x15, 0x00, # ..Logical Minimum (0) 100
+ 0x26, 0xff, 0x00, # ..Logical Maximum (255) 102
+ 0x35, 0x00, # ..Physical Minimum (0) 105
+ 0x46, 0xff, 0x00, # ..Physical Maximum (255) 107
+ 0x75, 0x08, # ..Report Size (8) 110
+ 0x95, 0x06, # ..Report Count (6) 112
+ 0x81, 0x02, # ..Input (Data,Var,Abs) 114
+ 0xc0, # .End Collection 116
+ 0x85, 0x02, # .Report ID (2) 117
+ 0x05, 0x08, # .Usage Page (LEDs) 119
+ 0x0a, 0x01, 0x00, # .Usage (Num Lock) 121
+ 0x0a, 0x02, 0x00, # .Usage (Caps Lock) 124
+ 0x0a, 0x03, 0x00, # .Usage (Scroll Lock) 127
+ 0x0a, 0x04, 0x00, # .Usage (Compose) 130
+ 0x15, 0x00, # .Logical Minimum (0) 133
+ 0x25, 0x01, # .Logical Maximum (1) 135
+ 0x75, 0x01, # .Report Size (1) 137
+ 0x95, 0x04, # .Report Count (4) 139
+ 0x91, 0x02, # .Output (Data,Var,Abs) 141
+ 0x75, 0x04, # .Report Size (4) 143
+ 0x95, 0x01, # .Report Count (1) 145
+ 0x91, 0x03, # .Output (Cnst,Var,Abs) 147
+ 0xc0, # End Collection 149
+ 0x05, 0x0c, # Usage Page (Consumer Devices) 150
+ 0x09, 0x01, # Usage (Consumer Control) 152
+ 0xa1, 0x01, # Collection (Application) 154
+ 0x85, 0x03, # .Report ID (3) 156
+ 0x05, 0x01, # .Usage Page (Generic Desktop) 158
+ 0x09, 0x06, # .Usage (Keyboard) 160
+ 0xa1, 0x02, # .Collection (Logical) 162
+ 0x05, 0x06, # ..Usage Page (Generic Device Controls) 164
+ 0x09, 0x20, # ..Usage (Battery Strength) 166
+ 0x15, 0x00, # ..Logical Minimum (0) 168
+ 0x26, 0xff, 0x00, # ..Logical Maximum (255) 170
+ 0x75, 0x08, # ..Report Size (8) 173
+ 0x95, 0x01, # ..Report Count (1) 175
+ 0x81, 0x02, # ..Input (Data,Var,Abs) 177
+ 0x06, 0xbc, 0xff, # ..Usage Page (Vendor Usage Page 0xffbc) 179
+ 0x0a, 0xad, 0xbd, # ..Usage (Vendor Usage 0xbdad) 182
+ 0x75, 0x08, # ..Report Size (8) 185
+ 0x95, 0x06, # ..Report Count (6) 187
+ 0x81, 0x02, # ..Input (Data,Var,Abs) 189
+ 0xc0, # .End Collection 191
+ 0xc0, # End Collection 192
+ ]
+ # fmt: on
+
+ def __init__(self, rdesc=report_descriptor, name=None):
+ super().__init__(rdesc, name=name, input_info=(BusType.USB, 0x18D1, 0x2C40))
+ self.buttons = (1, 2, 4, 5, 7, 8, 14, 15, 13)
+
+
+class RaptorMach2Joystick(JoystickGamepad):
+ axes_map = {
+ "left_stick": {
+ "x": AxisMapping("x"),
+ "y": AxisMapping("y"),
+ },
+ "right_stick": {
+ "x": AxisMapping("z"),
+ "y": AxisMapping("Rz"),
+ },
+ }
+
+ def __init__(
+ self,
+ name,
+ rdesc=None,
+ application="Joystick",
+ input_info=(BusType.USB, 0x11C0, 0x5606),
+ ):
+ super().__init__(rdesc, application, name, input_info)
+ self.buttons = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
+ self.hat_switch = 240 # null value is 240 as max is 239
+
+ def event(
+ self, *, left=(None, None), right=(None, None), hat_switch=None, buttons=None
+ ):
+ if hat_switch is not None:
+ hat_switch *= 30
+
+ return super().event(
+ left=left, right=right, hat_switch=hat_switch, buttons=buttons
+ )
+
+
+class TestSaitekGamepad(BaseTest.TestGamepad):
+ def create_device(self):
+ return SaitekGamepad()
+
+
+class TestAsusGamepad(BaseTest.TestGamepad):
+ def create_device(self):
+ return AsusGamepad()
+
+
+class TestRaptorMach2Joystick(BaseTest.TestGamepad):
+ hid_bpfs = [("FR-TEC__Raptor-Mach-2.bpf.o", True)]
+
+ def create_device(self):
+ return RaptorMach2Joystick(
+ "uhid test Sanmos Group FR-TEC Raptor MACH 2",
+ rdesc="05 01 09 04 a1 01 05 01 85 01 05 01 09 30 75 10 95 01 15 00 26 ff 07 46 ff 07 81 02 05 01 09 31 75 10 95 01 15 00 26 ff 07 46 ff 07 81 02 05 01 09 33 75 10 95 01 15 00 26 ff 03 46 ff 03 81 02 05 00 09 00 75 10 95 01 15 00 26 ff 03 46 ff 03 81 02 05 01 09 32 75 10 95 01 15 00 26 ff 03 46 ff 03 81 02 05 01 09 35 75 10 95 01 15 00 26 ff 03 46 ff 03 81 02 05 01 09 34 75 10 95 01 15 00 26 ff 07 46 ff 07 81 02 05 01 09 36 75 10 95 01 15 00 26 ff 03 46 ff 03 81 02 05 09 19 01 2a 1d 00 15 00 25 01 75 01 96 80 00 81 02 05 01 09 39 26 ef 00 46 68 01 65 14 75 10 95 01 81 42 05 01 09 00 75 08 95 1d 81 01 15 00 26 ef 00 85 58 26 ff 00 46 ff 00 75 08 95 3f 09 00 91 02 85 59 75 08 95 80 09 00 b1 02 c0",
+ input_info=(BusType.USB, 0x11C0, 0x5606),
+ )
diff --git a/tools/testing/selftests/hid/tests/test_hid_core.py b/tools/testing/selftests/hid/tests/test_hid_core.py
new file mode 100644
index 000000000000..9a7fe40020d2
--- /dev/null
+++ b/tools/testing/selftests/hid/tests/test_hid_core.py
@@ -0,0 +1,154 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2017 Red Hat, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+# This is for generic devices
+
+from . import base
+import logging
+
+logger = logging.getLogger("hidtools.test.hid")
+
+
+class TestCollectionOverflow(base.BaseTestCase.TestUhid):
+ """
+ Test class to test re-allocation of the HID collection stack in
+ hid-core.c.
+ """
+
+ def create_device(self):
+ # fmt: off
+ report_descriptor = [
+ 0x05, 0x01, # .Usage Page (Generic Desktop)
+ 0x09, 0x02, # .Usage (Mouse)
+ 0xa1, 0x01, # .Collection (Application)
+ 0x09, 0x02, # ..Usage (Mouse)
+ 0xa1, 0x02, # ..Collection (Logical)
+ 0x09, 0x01, # ...Usage (Pointer)
+ 0xa1, 0x00, # ...Collection (Physical)
+ 0x05, 0x09, # ....Usage Page (Button)
+ 0x19, 0x01, # ....Usage Minimum (1)
+ 0x29, 0x03, # ....Usage Maximum (3)
+ 0x15, 0x00, # ....Logical Minimum (0)
+ 0x25, 0x01, # ....Logical Maximum (1)
+ 0x75, 0x01, # ....Report Size (1)
+ 0x95, 0x03, # ....Report Count (3)
+ 0x81, 0x02, # ....Input (Data,Var,Abs)
+ 0x75, 0x05, # ....Report Size (5)
+ 0x95, 0x01, # ....Report Count (1)
+ 0x81, 0x03, # ....Input (Cnst,Var,Abs)
+ 0xa1, 0x02, # ....Collection (Logical)
+ 0x09, 0x01, # .....Usage (Pointer)
+ 0xa1, 0x02, # ....Collection (Logical)
+ 0x09, 0x01, # .....Usage (Pointer)
+ 0xa1, 0x02, # ....Collection (Logical)
+ 0x09, 0x01, # .....Usage (Pointer)
+ 0xa1, 0x02, # ....Collection (Logical)
+ 0x09, 0x01, # .....Usage (Pointer)
+ 0xa1, 0x02, # ....Collection (Logical)
+ 0x09, 0x01, # .....Usage (Pointer)
+ 0xa1, 0x02, # ....Collection (Logical)
+ 0x09, 0x01, # .....Usage (Pointer)
+ 0xa1, 0x02, # ....Collection (Logical)
+ 0x09, 0x01, # .....Usage (Pointer)
+ 0xa1, 0x02, # ....Collection (Logical)
+ 0x09, 0x01, # .....Usage (Pointer)
+ 0xa1, 0x02, # ....Collection (Logical)
+ 0x09, 0x01, # .....Usage (Pointer)
+ 0xa1, 0x02, # ....Collection (Logical)
+ 0x09, 0x01, # .....Usage (Pointer)
+ 0xa1, 0x02, # ....Collection (Logical)
+ 0x09, 0x01, # .....Usage (Pointer)
+ 0xa1, 0x02, # ....Collection (Logical)
+ 0x09, 0x01, # .....Usage (Pointer)
+ 0xa1, 0x02, # ....Collection (Logical)
+ 0x09, 0x01, # .....Usage (Pointer)
+ 0xa1, 0x02, # ....Collection (Logical)
+ 0x09, 0x01, # .....Usage (Pointer)
+ 0xa1, 0x02, # ....Collection (Logical)
+ 0x09, 0x01, # .....Usage (Pointer)
+ 0xa1, 0x02, # ....Collection (Logical)
+ 0x09, 0x01, # .....Usage (Pointer)
+ 0xa1, 0x02, # ....Collection (Logical)
+ 0x09, 0x01, # .....Usage (Pointer)
+ 0x05, 0x01, # .....Usage Page (Generic Desktop)
+ 0x09, 0x30, # .....Usage (X)
+ 0x09, 0x31, # .....Usage (Y)
+ 0x15, 0x81, # .....Logical Minimum (-127)
+ 0x25, 0x7f, # .....Logical Maximum (127)
+ 0x75, 0x08, # .....Report Size (8)
+ 0x95, 0x02, # .....Report Count (2)
+ 0x81, 0x06, # .....Input (Data,Var,Rel)
+ 0xa1, 0x02, # ...Collection (Logical)
+ 0x85, 0x12, # ....Report ID (18)
+ 0x09, 0x48, # ....Usage (Resolution Multiplier)
+ 0x95, 0x01, # ....Report Count (1)
+ 0x75, 0x02, # ....Report Size (2)
+ 0x15, 0x00, # ....Logical Minimum (0)
+ 0x25, 0x01, # ....Logical Maximum (1)
+ 0x35, 0x01, # ....Physical Minimum (1)
+ 0x45, 0x0c, # ....Physical Maximum (12)
+ 0xb1, 0x02, # ....Feature (Data,Var,Abs)
+ 0x85, 0x1a, # ....Report ID (26)
+ 0x09, 0x38, # ....Usage (Wheel)
+ 0x35, 0x00, # ....Physical Minimum (0)
+ 0x45, 0x00, # ....Physical Maximum (0)
+ 0x95, 0x01, # ....Report Count (1)
+ 0x75, 0x10, # ....Report Size (16)
+ 0x16, 0x01, 0x80, # ....Logical Minimum (-32767)
+ 0x26, 0xff, 0x7f, # ....Logical Maximum (32767)
+ 0x81, 0x06, # ....Input (Data,Var,Rel)
+ 0xc0, # ...End Collection
+ 0xc0, # ...End Collection
+ 0xc0, # ...End Collection
+ 0xc0, # ...End Collection
+ 0xc0, # ...End Collection
+ 0xc0, # ...End Collection
+ 0xc0, # ...End Collection
+ 0xc0, # ...End Collection
+ 0xc0, # ...End Collection
+ 0xc0, # ...End Collection
+ 0xc0, # ...End Collection
+ 0xc0, # ...End Collection
+ 0xc0, # ...End Collection
+ 0xc0, # ...End Collection
+ 0xc0, # ...End Collection
+ 0xc0, # ...End Collection
+ 0xc0, # ...End Collection
+ 0xc0, # ...End Collection
+ 0xc0, # ...End Collection
+ 0xc0, # ..End Collection
+ 0xc0, # .End Collection
+ ]
+ # fmt: on
+ return base.UHIDTestDevice(
+ name=None, rdesc=report_descriptor, application="Mouse"
+ )
+
+ def test_rdesc(self):
+ """
+ This test can only check for negatives. If the kernel crashes, you
+ know why. If this test passes, either the bug isn't present or just
+ didn't get triggered. No way to know.
+
+ For an explanation, see kernel patch
+ HID: core: replace the collection tree pointers with indices
+ """
+ pass
diff --git a/tools/testing/selftests/hid/tests/test_ite_keyboard.py b/tools/testing/selftests/hid/tests/test_ite_keyboard.py
new file mode 100644
index 000000000000..38550c167bae
--- /dev/null
+++ b/tools/testing/selftests/hid/tests/test_ite_keyboard.py
@@ -0,0 +1,166 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2020 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2020 Red Hat, Inc.
+#
+
+from .test_keyboard import ArrayKeyboard, TestArrayKeyboard
+from hidtools.util import BusType
+
+import libevdev
+import logging
+
+logger = logging.getLogger("hidtools.test.ite-keyboard")
+
+KERNEL_MODULE = ("itetech", "hid_ite")
+
+
+class KbdData(object):
+ pass
+
+
+# The ITE keyboards have an issue regarding the Wifi key:
+# nothing comes in when pressing the key, but we get a null
+# event on the key release.
+# This test covers this case.
+class ITEKeyboard(ArrayKeyboard):
+ # fmt: off
+ report_descriptor = [
+ 0x06, 0x85, 0xff, # Usage Page (Vendor Usage Page 0xff85)
+ 0x09, 0x95, # Usage (Vendor Usage 0x95) 3
+ 0xa1, 0x01, # Collection (Application) 5
+ 0x85, 0x5a, # .Report ID (90) 7
+ 0x09, 0x01, # .Usage (Vendor Usage 0x01) 9
+ 0x15, 0x00, # .Logical Minimum (0) 11
+ 0x26, 0xff, 0x00, # .Logical Maximum (255) 13
+ 0x75, 0x08, # .Report Size (8) 16
+ 0x95, 0x10, # .Report Count (16) 18
+ 0xb1, 0x00, # .Feature (Data,Arr,Abs) 20
+ 0xc0, # End Collection 22
+ 0x05, 0x01, # Usage Page (Generic Desktop) 23
+ 0x09, 0x06, # Usage (Keyboard) 25
+ 0xa1, 0x01, # Collection (Application) 27
+ 0x85, 0x01, # .Report ID (1) 29
+ 0x75, 0x01, # .Report Size (1) 31
+ 0x95, 0x08, # .Report Count (8) 33
+ 0x05, 0x07, # .Usage Page (Keyboard) 35
+ 0x19, 0xe0, # .Usage Minimum (224) 37
+ 0x29, 0xe7, # .Usage Maximum (231) 39
+ 0x15, 0x00, # .Logical Minimum (0) 41
+ 0x25, 0x01, # .Logical Maximum (1) 43
+ 0x81, 0x02, # .Input (Data,Var,Abs) 45
+ 0x95, 0x01, # .Report Count (1) 47
+ 0x75, 0x08, # .Report Size (8) 49
+ 0x81, 0x03, # .Input (Cnst,Var,Abs) 51
+ 0x95, 0x05, # .Report Count (5) 53
+ 0x75, 0x01, # .Report Size (1) 55
+ 0x05, 0x08, # .Usage Page (LEDs) 57
+ 0x19, 0x01, # .Usage Minimum (1) 59
+ 0x29, 0x05, # .Usage Maximum (5) 61
+ 0x91, 0x02, # .Output (Data,Var,Abs) 63
+ 0x95, 0x01, # .Report Count (1) 65
+ 0x75, 0x03, # .Report Size (3) 67
+ 0x91, 0x03, # .Output (Cnst,Var,Abs) 69
+ 0x95, 0x06, # .Report Count (6) 71
+ 0x75, 0x08, # .Report Size (8) 73
+ 0x15, 0x00, # .Logical Minimum (0) 75
+ 0x26, 0xff, 0x00, # .Logical Maximum (255) 77
+ 0x05, 0x07, # .Usage Page (Keyboard) 80
+ 0x19, 0x00, # .Usage Minimum (0) 82
+ 0x2a, 0xff, 0x00, # .Usage Maximum (255) 84
+ 0x81, 0x00, # .Input (Data,Arr,Abs) 87
+ 0xc0, # End Collection 89
+ 0x05, 0x0c, # Usage Page (Consumer Devices) 90
+ 0x09, 0x01, # Usage (Consumer Control) 92
+ 0xa1, 0x01, # Collection (Application) 94
+ 0x85, 0x02, # .Report ID (2) 96
+ 0x19, 0x00, # .Usage Minimum (0) 98
+ 0x2a, 0x3c, 0x02, # .Usage Maximum (572) 100
+ 0x15, 0x00, # .Logical Minimum (0) 103
+ 0x26, 0x3c, 0x02, # .Logical Maximum (572) 105
+ 0x75, 0x10, # .Report Size (16) 108
+ 0x95, 0x01, # .Report Count (1) 110
+ 0x81, 0x00, # .Input (Data,Arr,Abs) 112
+ 0xc0, # End Collection 114
+ 0x05, 0x01, # Usage Page (Generic Desktop) 115
+ 0x09, 0x0c, # Usage (Wireless Radio Controls) 117
+ 0xa1, 0x01, # Collection (Application) 119
+ 0x85, 0x03, # .Report ID (3) 121
+ 0x15, 0x00, # .Logical Minimum (0) 123
+ 0x25, 0x01, # .Logical Maximum (1) 125
+ 0x09, 0xc6, # .Usage (Wireless Radio Button) 127
+ 0x95, 0x01, # .Report Count (1) 129
+ 0x75, 0x01, # .Report Size (1) 131
+ 0x81, 0x06, # .Input (Data,Var,Rel) 133
+ 0x75, 0x07, # .Report Size (7) 135
+ 0x81, 0x03, # .Input (Cnst,Var,Abs) 137
+ 0xc0, # End Collection 139
+ 0x05, 0x88, # Usage Page (Vendor Usage Page 0x88) 140
+ 0x09, 0x01, # Usage (Vendor Usage 0x01) 142
+ 0xa1, 0x01, # Collection (Application) 144
+ 0x85, 0x04, # .Report ID (4) 146
+ 0x19, 0x00, # .Usage Minimum (0) 148
+ 0x2a, 0xff, 0xff, # .Usage Maximum (65535) 150
+ 0x15, 0x00, # .Logical Minimum (0) 153
+ 0x26, 0xff, 0xff, # .Logical Maximum (65535) 155
+ 0x75, 0x08, # .Report Size (8) 158
+ 0x95, 0x02, # .Report Count (2) 160
+ 0x81, 0x02, # .Input (Data,Var,Abs) 162
+ 0xc0, # End Collection 164
+ 0x05, 0x01, # Usage Page (Generic Desktop) 165
+ 0x09, 0x80, # Usage (System Control) 167
+ 0xa1, 0x01, # Collection (Application) 169
+ 0x85, 0x05, # .Report ID (5) 171
+ 0x19, 0x81, # .Usage Minimum (129) 173
+ 0x29, 0x83, # .Usage Maximum (131) 175
+ 0x15, 0x00, # .Logical Minimum (0) 177
+ 0x25, 0x01, # .Logical Maximum (1) 179
+ 0x95, 0x08, # .Report Count (8) 181
+ 0x75, 0x01, # .Report Size (1) 183
+ 0x81, 0x02, # .Input (Data,Var,Abs) 185
+ 0xc0, # End Collection 187
+ ]
+ # fmt: on
+
+ def __init__(
+ self,
+ rdesc=report_descriptor,
+ name=None,
+ input_info=(BusType.USB, 0x06CB, 0x2968),
+ ):
+ super().__init__(rdesc, name, input_info)
+
+ def event(self, keys, reportID=None, application=None):
+ application = application or "Keyboard"
+ return super().event(keys, reportID, application)
+
+
+class TestITEKeyboard(TestArrayKeyboard):
+ kernel_modules = [KERNEL_MODULE]
+
+ def create_device(self):
+ return ITEKeyboard()
+
+ def test_wifi_key(self):
+ uhdev = self.uhdev
+ syn_event = self.syn_event
+
+ # the following sends a 'release' event on the Wifi key.
+ # the kernel is supposed to translate this into Wifi key
+ # down and up
+ r = [0x03, 0x00]
+ uhdev.call_input_event(r)
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_RFKILL, 1))
+ events = uhdev.next_sync_events()
+ self.debug_reports([r], uhdev, events)
+ self.assertInputEventsIn(expected, events)
+
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_RFKILL, 0))
+ # the kernel sends the two down/up key events in a batch, no need to
+ # call events = uhdev.next_sync_events()
+ self.debug_reports([], uhdev, events)
+ self.assertInputEventsIn(expected, events)
diff --git a/tools/testing/selftests/hid/tests/test_keyboard.py b/tools/testing/selftests/hid/tests/test_keyboard.py
new file mode 100644
index 000000000000..b3b2bdbf63b7
--- /dev/null
+++ b/tools/testing/selftests/hid/tests/test_keyboard.py
@@ -0,0 +1,485 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2018 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2018 Red Hat, Inc.
+#
+
+from . import base
+import hidtools.hid
+import libevdev
+import logging
+
+logger = logging.getLogger("hidtools.test.keyboard")
+
+
+class InvalidHIDCommunication(Exception):
+ pass
+
+
+class KeyboardData(object):
+ pass
+
+
+class BaseKeyboard(base.UHIDTestDevice):
+ def __init__(self, rdesc, name=None, input_info=None):
+ assert rdesc is not None
+ super().__init__(name, "Key", input_info=input_info, rdesc=rdesc)
+ self.keystates = {}
+
+ def _update_key_state(self, keys):
+ """
+ Update the internal state of keys with the new state given.
+
+ :param key: a tuple of chars for the currently pressed keys.
+ """
+ # First remove the already released keys
+ unused_keys = [k for k, v in self.keystates.items() if not v]
+ for key in unused_keys:
+ del self.keystates[key]
+
+ # self.keystates contains now the list of currently pressed keys,
+ # release them...
+ for key in self.keystates.keys():
+ self.keystates[key] = False
+
+ # ...and press those that are in parameter
+ for key in keys:
+ self.keystates[key] = True
+
+ def _create_report_data(self):
+ keyboard = KeyboardData()
+ for key, value in self.keystates.items():
+ key = key.replace(" ", "").lower()
+ setattr(keyboard, key, value)
+ return keyboard
+
+ def create_array_report(self, keys, reportID=None, application=None):
+ """
+ Return an input report for this device.
+
+ :param keys: a tuple of chars for the pressed keys. The class maintains
+ the list of currently pressed keys, so to release a key, the caller
+ needs to call again this function without the key in this tuple.
+ :param reportID: the numeric report ID for this report, if needed
+ """
+ self._update_key_state(keys)
+ reportID = reportID or self.default_reportID
+
+ keyboard = self._create_report_data()
+ return self.create_report(keyboard, reportID=reportID, application=application)
+
+ def event(self, keys, reportID=None, application=None):
+ """
+ Send an input event on the default report ID.
+
+ :param keys: a tuple of chars for the pressed keys. The class maintains
+ the list of currently pressed keys, so to release a key, the caller
+ needs to call again this function without the key in this tuple.
+ """
+ r = self.create_array_report(keys, reportID, application)
+ self.call_input_event(r)
+ return [r]
+
+
+class PlainKeyboard(BaseKeyboard):
+ # fmt: off
+ report_descriptor = [
+ 0x05, 0x01, # Usage Page (Generic Desktop)
+ 0x09, 0x06, # Usage (Keyboard)
+ 0xa1, 0x01, # Collection (Application)
+ 0x85, 0x01, # .Report ID (1)
+ 0x05, 0x07, # .Usage Page (Keyboard)
+ 0x19, 0xe0, # .Usage Minimum (224)
+ 0x29, 0xe7, # .Usage Maximum (231)
+ 0x15, 0x00, # .Logical Minimum (0)
+ 0x25, 0x01, # .Logical Maximum (1)
+ 0x75, 0x01, # .Report Size (1)
+ 0x95, 0x08, # .Report Count (8)
+ 0x81, 0x02, # .Input (Data,Var,Abs)
+ 0x19, 0x00, # .Usage Minimum (0)
+ 0x29, 0x97, # .Usage Maximum (151)
+ 0x15, 0x00, # .Logical Minimum (0)
+ 0x25, 0x01, # .Logical Maximum (1)
+ 0x75, 0x01, # .Report Size (1)
+ 0x95, 0x98, # .Report Count (152)
+ 0x81, 0x02, # .Input (Data,Var,Abs)
+ 0xc0, # End Collection
+ ]
+ # fmt: on
+
+ def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
+ super().__init__(rdesc, name, input_info)
+ self.default_reportID = 1
+
+
+class ArrayKeyboard(BaseKeyboard):
+ # fmt: off
+ report_descriptor = [
+ 0x05, 0x01, # Usage Page (Generic Desktop)
+ 0x09, 0x06, # Usage (Keyboard)
+ 0xa1, 0x01, # Collection (Application)
+ 0x05, 0x07, # .Usage Page (Keyboard)
+ 0x19, 0xe0, # .Usage Minimum (224)
+ 0x29, 0xe7, # .Usage Maximum (231)
+ 0x15, 0x00, # .Logical Minimum (0)
+ 0x25, 0x01, # .Logical Maximum (1)
+ 0x75, 0x01, # .Report Size (1)
+ 0x95, 0x08, # .Report Count (8)
+ 0x81, 0x02, # .Input (Data,Var,Abs)
+ 0x95, 0x06, # .Report Count (6)
+ 0x75, 0x08, # .Report Size (8)
+ 0x15, 0x00, # .Logical Minimum (0)
+ 0x26, 0xa4, 0x00, # .Logical Maximum (164)
+ 0x05, 0x07, # .Usage Page (Keyboard)
+ 0x19, 0x00, # .Usage Minimum (0)
+ 0x29, 0xa4, # .Usage Maximum (164)
+ 0x81, 0x00, # .Input (Data,Arr,Abs)
+ 0xc0, # End Collection
+ ]
+ # fmt: on
+
+ def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
+ super().__init__(rdesc, name, input_info)
+
+ def _create_report_data(self):
+ data = KeyboardData()
+ array = []
+
+ hut = hidtools.hut.HUT
+
+ # strip modifiers from the array
+ for k, v in self.keystates.items():
+ # we ignore depressed keys
+ if not v:
+ continue
+
+ usage = hut[0x07].from_name[k].usage
+ if usage >= 224 and usage <= 231:
+ # modifier
+ setattr(data, k.lower(), 1)
+ else:
+ array.append(k)
+
+ # if array length is bigger than 6, report ErrorRollOver
+ if len(array) > 6:
+ array = ["ErrorRollOver"] * 6
+
+ data.keyboard = array
+ return data
+
+
+class LEDKeyboard(ArrayKeyboard):
+ # fmt: off
+ report_descriptor = [
+ 0x05, 0x01, # Usage Page (Generic Desktop)
+ 0x09, 0x06, # Usage (Keyboard)
+ 0xa1, 0x01, # Collection (Application)
+ 0x05, 0x07, # .Usage Page (Keyboard)
+ 0x19, 0xe0, # .Usage Minimum (224)
+ 0x29, 0xe7, # .Usage Maximum (231)
+ 0x15, 0x00, # .Logical Minimum (0)
+ 0x25, 0x01, # .Logical Maximum (1)
+ 0x75, 0x01, # .Report Size (1)
+ 0x95, 0x08, # .Report Count (8)
+ 0x81, 0x02, # .Input (Data,Var,Abs)
+ 0x95, 0x01, # .Report Count (1)
+ 0x75, 0x08, # .Report Size (8)
+ 0x81, 0x01, # .Input (Cnst,Arr,Abs)
+ 0x95, 0x05, # .Report Count (5)
+ 0x75, 0x01, # .Report Size (1)
+ 0x05, 0x08, # .Usage Page (LEDs)
+ 0x19, 0x01, # .Usage Minimum (1)
+ 0x29, 0x05, # .Usage Maximum (5)
+ 0x91, 0x02, # .Output (Data,Var,Abs)
+ 0x95, 0x01, # .Report Count (1)
+ 0x75, 0x03, # .Report Size (3)
+ 0x91, 0x01, # .Output (Cnst,Arr,Abs)
+ 0x95, 0x06, # .Report Count (6)
+ 0x75, 0x08, # .Report Size (8)
+ 0x15, 0x00, # .Logical Minimum (0)
+ 0x26, 0xa4, 0x00, # .Logical Maximum (164)
+ 0x05, 0x07, # .Usage Page (Keyboard)
+ 0x19, 0x00, # .Usage Minimum (0)
+ 0x29, 0xa4, # .Usage Maximum (164)
+ 0x81, 0x00, # .Input (Data,Arr,Abs)
+ 0xc0, # End Collection
+ ]
+ # fmt: on
+
+ def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
+ super().__init__(rdesc, name, input_info)
+
+
+# Some Primax manufactured keyboards set the Usage Page after having defined
+# some local Usages. It relies on the fact that the specification states that
+# Usages are to be concatenated with Usage Pages upon finding a Main item (see
+# 6.2.2.8). This test covers this case.
+class PrimaxKeyboard(ArrayKeyboard):
+ # fmt: off
+ report_descriptor = [
+ 0x05, 0x01, # Usage Page (Generic Desktop)
+ 0x09, 0x06, # Usage (Keyboard)
+ 0xA1, 0x01, # Collection (Application)
+ 0x05, 0x07, # .Usage Page (Keyboard)
+ 0x19, 0xE0, # .Usage Minimum (224)
+ 0x29, 0xE7, # .Usage Maximum (231)
+ 0x15, 0x00, # .Logical Minimum (0)
+ 0x25, 0x01, # .Logical Maximum (1)
+ 0x75, 0x01, # .Report Size (1)
+ 0x95, 0x08, # .Report Count (8)
+ 0x81, 0x02, # .Input (Data,Var,Abs)
+ 0x75, 0x08, # .Report Size (8)
+ 0x95, 0x01, # .Report Count (1)
+ 0x81, 0x01, # .Input (Data,Var,Abs)
+ 0x05, 0x08, # .Usage Page (LEDs)
+ 0x19, 0x01, # .Usage Minimum (1)
+ 0x29, 0x03, # .Usage Maximum (3)
+ 0x75, 0x01, # .Report Size (1)
+ 0x95, 0x03, # .Report Count (3)
+ 0x91, 0x02, # .Output (Data,Var,Abs)
+ 0x95, 0x01, # .Report Count (1)
+ 0x75, 0x05, # .Report Size (5)
+ 0x91, 0x01, # .Output (Constant)
+ 0x15, 0x00, # .Logical Minimum (0)
+ 0x26, 0xFF, 0x00, # .Logical Maximum (255)
+ 0x19, 0x00, # .Usage Minimum (0)
+ 0x2A, 0xFF, 0x00, # .Usage Maximum (255)
+ 0x05, 0x07, # .Usage Page (Keyboard)
+ 0x75, 0x08, # .Report Size (8)
+ 0x95, 0x06, # .Report Count (6)
+ 0x81, 0x00, # .Input (Data,Arr,Abs)
+ 0xC0, # End Collection
+ ]
+ # fmt: on
+
+ def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
+ super().__init__(rdesc, name, input_info)
+
+
+class BaseTest:
+ class TestKeyboard(base.BaseTestCase.TestUhid):
+ def test_single_key(self):
+ """check for key reliability."""
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+ syn_event = self.syn_event
+
+ r = uhdev.event(["a and A"])
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_A, 1))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+ assert evdev.value[libevdev.EV_KEY.KEY_A] == 1
+
+ r = uhdev.event([])
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_A, 0))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+ assert evdev.value[libevdev.EV_KEY.KEY_A] == 0
+
+ def test_two_keys(self):
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+ syn_event = self.syn_event
+
+ r = uhdev.event(["a and A", "q and Q"])
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_A, 1))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_Q, 1))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+ assert evdev.value[libevdev.EV_KEY.KEY_A] == 1
+
+ r = uhdev.event([])
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_A, 0))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_Q, 0))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+ assert evdev.value[libevdev.EV_KEY.KEY_A] == 0
+ assert evdev.value[libevdev.EV_KEY.KEY_Q] == 0
+
+ r = uhdev.event(["c and C"])
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_C, 1))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+ assert evdev.value[libevdev.EV_KEY.KEY_C] == 1
+
+ r = uhdev.event(["c and C", "Spacebar"])
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_SPACE, 1))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ assert libevdev.InputEvent(libevdev.EV_KEY.KEY_C) not in events
+ self.assertInputEventsIn(expected, events)
+ assert evdev.value[libevdev.EV_KEY.KEY_C] == 1
+ assert evdev.value[libevdev.EV_KEY.KEY_SPACE] == 1
+
+ r = uhdev.event(["Spacebar"])
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_C, 0))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ assert libevdev.InputEvent(libevdev.EV_KEY.KEY_SPACE) not in events
+ self.assertInputEventsIn(expected, events)
+ assert evdev.value[libevdev.EV_KEY.KEY_C] == 0
+ assert evdev.value[libevdev.EV_KEY.KEY_SPACE] == 1
+
+ r = uhdev.event([])
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_SPACE, 0))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+ assert evdev.value[libevdev.EV_KEY.KEY_SPACE] == 0
+
+ def test_modifiers(self):
+ # ctrl-alt-del would be very nice :)
+ uhdev = self.uhdev
+ syn_event = self.syn_event
+
+ r = uhdev.event(["LeftControl", "LeftShift", "= and +"])
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_LEFTCTRL, 1))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_LEFTSHIFT, 1))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_EQUAL, 1))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+
+
+class TestPlainKeyboard(BaseTest.TestKeyboard):
+ def create_device(self):
+ return PlainKeyboard()
+
+ def test_10_keys(self):
+ uhdev = self.uhdev
+ syn_event = self.syn_event
+
+ r = uhdev.event(
+ [
+ "1 and !",
+ "2 and @",
+ "3 and #",
+ "4 and $",
+ "5 and %",
+ "6 and ^",
+ "7 and &",
+ "8 and *",
+ "9 and (",
+ "0 and )",
+ ]
+ )
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_0, 1))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_1, 1))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_2, 1))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_3, 1))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_4, 1))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_5, 1))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_6, 1))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_7, 1))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_8, 1))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_9, 1))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+
+ r = uhdev.event([])
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_0, 0))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_1, 0))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_2, 0))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_3, 0))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_4, 0))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_5, 0))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_6, 0))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_7, 0))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_8, 0))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_9, 0))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+
+
+class TestArrayKeyboard(BaseTest.TestKeyboard):
+ def create_device(self):
+ return ArrayKeyboard()
+
+ def test_10_keys(self):
+ uhdev = self.uhdev
+ syn_event = self.syn_event
+
+ r = uhdev.event(
+ [
+ "1 and !",
+ "2 and @",
+ "3 and #",
+ "4 and $",
+ "5 and %",
+ "6 and ^",
+ ]
+ )
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_1, 1))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_2, 1))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_3, 1))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_4, 1))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_5, 1))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_6, 1))
+ events = uhdev.next_sync_events()
+
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+
+ # ErrRollOver
+ r = uhdev.event(
+ [
+ "1 and !",
+ "2 and @",
+ "3 and #",
+ "4 and $",
+ "5 and %",
+ "6 and ^",
+ "7 and &",
+ "8 and *",
+ "9 and (",
+ "0 and )",
+ ]
+ )
+ events = uhdev.next_sync_events()
+
+ self.debug_reports(r, uhdev, events)
+
+ assert len(events) == 0
+
+ r = uhdev.event([])
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_1, 0))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_2, 0))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_3, 0))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_4, 0))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_5, 0))
+ expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_6, 0))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(expected, events)
+
+
+class TestLEDKeyboard(BaseTest.TestKeyboard):
+ def create_device(self):
+ return LEDKeyboard()
+
+
+class TestPrimaxKeyboard(BaseTest.TestKeyboard):
+ def create_device(self):
+ return PrimaxKeyboard()
diff --git a/tools/testing/selftests/hid/tests/test_mouse.py b/tools/testing/selftests/hid/tests/test_mouse.py
new file mode 100644
index 000000000000..66daf7e5975c
--- /dev/null
+++ b/tools/testing/selftests/hid/tests/test_mouse.py
@@ -0,0 +1,977 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2017 Red Hat, Inc.
+#
+
+from . import base
+import hidtools.hid
+from hidtools.util import BusType
+import libevdev
+import logging
+import pytest
+
+logger = logging.getLogger("hidtools.test.mouse")
+
+# workaround https://gitlab.freedesktop.org/libevdev/python-libevdev/issues/6
+try:
+ libevdev.EV_REL.REL_WHEEL_HI_RES
+except AttributeError:
+ libevdev.EV_REL.REL_WHEEL_HI_RES = libevdev.EV_REL.REL_0B
+ libevdev.EV_REL.REL_HWHEEL_HI_RES = libevdev.EV_REL.REL_0C
+
+
+class InvalidHIDCommunication(Exception):
+ pass
+
+
+class MouseData(object):
+ pass
+
+
+class BaseMouse(base.UHIDTestDevice):
+ def __init__(self, rdesc, name=None, input_info=None):
+ assert rdesc is not None
+ super().__init__(name, "Mouse", input_info=input_info, rdesc=rdesc)
+ self.left = False
+ self.right = False
+ self.middle = False
+
+ def create_report(self, x, y, buttons=None, wheels=None, reportID=None):
+ """
+ Return an input report for this device.
+
+ :param x: relative x
+ :param y: relative y
+ :param buttons: a (l, r, m) tuple of bools for the button states,
+ where ``None`` is "leave unchanged"
+ :param wheels: a single value for the vertical wheel or a (vertical, horizontal) tuple for
+ the two wheels
+ :param reportID: the numeric report ID for this report, if needed
+ """
+ if buttons is not None:
+ left, right, middle = buttons
+ if left is not None:
+ self.left = left
+ if right is not None:
+ self.right = right
+ if middle is not None:
+ self.middle = middle
+ left = self.left
+ right = self.right
+ middle = self.middle
+ # Note: the BaseMouse doesn't actually have a wheel but the
+ # create_report magic only fills in those fields exist, so let's
+ # make this generic here.
+ wheel, acpan = 0, 0
+ if wheels is not None:
+ if isinstance(wheels, tuple):
+ wheel = wheels[0]
+ acpan = wheels[1]
+ else:
+ wheel = wheels
+
+ reportID = reportID or self.default_reportID
+
+ mouse = MouseData()
+ mouse.b1 = int(left)
+ mouse.b2 = int(right)
+ mouse.b3 = int(middle)
+ mouse.x = x
+ mouse.y = y
+ mouse.wheel = wheel
+ mouse.acpan = acpan
+ return super().create_report(mouse, reportID=reportID)
+
+ def event(self, x, y, buttons=None, wheels=None):
+ """
+ Send an input event on the default report ID.
+
+ :param x: relative x
+ :param y: relative y
+ :param buttons: a (l, r, m) tuple of bools for the button states,
+ where ``None`` is "leave unchanged"
+ :param wheels: a single value for the vertical wheel or a (vertical, horizontal) tuple for
+ the two wheels
+ """
+ r = self.create_report(x, y, buttons, wheels)
+ self.call_input_event(r)
+ return [r]
+
+
+class ButtonMouse(BaseMouse):
+ # fmt: off
+ report_descriptor = [
+ 0x05, 0x01, # .Usage Page (Generic Desktop) 0
+ 0x09, 0x02, # .Usage (Mouse) 2
+ 0xa1, 0x01, # .Collection (Application) 4
+ 0x09, 0x02, # ..Usage (Mouse) 6
+ 0xa1, 0x02, # ..Collection (Logical) 8
+ 0x09, 0x01, # ...Usage (Pointer) 10
+ 0xa1, 0x00, # ...Collection (Physical) 12
+ 0x05, 0x09, # ....Usage Page (Button) 14
+ 0x19, 0x01, # ....Usage Minimum (1) 16
+ 0x29, 0x03, # ....Usage Maximum (3) 18
+ 0x15, 0x00, # ....Logical Minimum (0) 20
+ 0x25, 0x01, # ....Logical Maximum (1) 22
+ 0x75, 0x01, # ....Report Size (1) 24
+ 0x95, 0x03, # ....Report Count (3) 26
+ 0x81, 0x02, # ....Input (Data,Var,Abs) 28
+ 0x75, 0x05, # ....Report Size (5) 30
+ 0x95, 0x01, # ....Report Count (1) 32
+ 0x81, 0x03, # ....Input (Cnst,Var,Abs) 34
+ 0x05, 0x01, # ....Usage Page (Generic Desktop) 36
+ 0x09, 0x30, # ....Usage (X) 38
+ 0x09, 0x31, # ....Usage (Y) 40
+ 0x15, 0x81, # ....Logical Minimum (-127) 42
+ 0x25, 0x7f, # ....Logical Maximum (127) 44
+ 0x75, 0x08, # ....Report Size (8) 46
+ 0x95, 0x02, # ....Report Count (2) 48
+ 0x81, 0x06, # ....Input (Data,Var,Rel) 50
+ 0xc0, # ...End Collection 52
+ 0xc0, # ..End Collection 53
+ 0xc0, # .End Collection 54
+ ]
+ # fmt: on
+
+ def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
+ super().__init__(rdesc, name, input_info)
+
+ def fake_report(self, x, y, buttons):
+ if buttons is not None:
+ left, right, middle = buttons
+ if left is None:
+ left = self.left
+ if right is None:
+ right = self.right
+ if middle is None:
+ middle = self.middle
+ else:
+ left = self.left
+ right = self.right
+ middle = self.middle
+
+ button_mask = sum(1 << i for i, b in enumerate([left, right, middle]) if b)
+ x = max(-127, min(127, x))
+ y = max(-127, min(127, y))
+ x = hidtools.util.to_twos_comp(x, 8)
+ y = hidtools.util.to_twos_comp(y, 8)
+ return [button_mask, x, y]
+
+
+class WheelMouse(ButtonMouse):
+ # fmt: off
+ report_descriptor = [
+ 0x05, 0x01, # Usage Page (Generic Desktop) 0
+ 0x09, 0x02, # Usage (Mouse) 2
+ 0xa1, 0x01, # Collection (Application) 4
+ 0x05, 0x09, # .Usage Page (Button) 6
+ 0x19, 0x01, # .Usage Minimum (1) 8
+ 0x29, 0x03, # .Usage Maximum (3) 10
+ 0x15, 0x00, # .Logical Minimum (0) 12
+ 0x25, 0x01, # .Logical Maximum (1) 14
+ 0x95, 0x03, # .Report Count (3) 16
+ 0x75, 0x01, # .Report Size (1) 18
+ 0x81, 0x02, # .Input (Data,Var,Abs) 20
+ 0x95, 0x01, # .Report Count (1) 22
+ 0x75, 0x05, # .Report Size (5) 24
+ 0x81, 0x03, # .Input (Cnst,Var,Abs) 26
+ 0x05, 0x01, # .Usage Page (Generic Desktop) 28
+ 0x09, 0x01, # .Usage (Pointer) 30
+ 0xa1, 0x00, # .Collection (Physical) 32
+ 0x09, 0x30, # ..Usage (X) 34
+ 0x09, 0x31, # ..Usage (Y) 36
+ 0x15, 0x81, # ..Logical Minimum (-127) 38
+ 0x25, 0x7f, # ..Logical Maximum (127) 40
+ 0x75, 0x08, # ..Report Size (8) 42
+ 0x95, 0x02, # ..Report Count (2) 44
+ 0x81, 0x06, # ..Input (Data,Var,Rel) 46
+ 0xc0, # .End Collection 48
+ 0x09, 0x38, # .Usage (Wheel) 49
+ 0x15, 0x81, # .Logical Minimum (-127) 51
+ 0x25, 0x7f, # .Logical Maximum (127) 53
+ 0x75, 0x08, # .Report Size (8) 55
+ 0x95, 0x01, # .Report Count (1) 57
+ 0x81, 0x06, # .Input (Data,Var,Rel) 59
+ 0xc0, # End Collection 61
+ ]
+ # fmt: on
+
+ def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
+ super().__init__(rdesc, name, input_info)
+ self.wheel_multiplier = 1
+
+
+class TwoWheelMouse(WheelMouse):
+ # fmt: off
+ report_descriptor = [
+ 0x05, 0x01, # Usage Page (Generic Desktop) 0
+ 0x09, 0x02, # Usage (Mouse) 2
+ 0xa1, 0x01, # Collection (Application) 4
+ 0x09, 0x01, # .Usage (Pointer) 6
+ 0xa1, 0x00, # .Collection (Physical) 8
+ 0x05, 0x09, # ..Usage Page (Button) 10
+ 0x19, 0x01, # ..Usage Minimum (1) 12
+ 0x29, 0x10, # ..Usage Maximum (16) 14
+ 0x15, 0x00, # ..Logical Minimum (0) 16
+ 0x25, 0x01, # ..Logical Maximum (1) 18
+ 0x95, 0x10, # ..Report Count (16) 20
+ 0x75, 0x01, # ..Report Size (1) 22
+ 0x81, 0x02, # ..Input (Data,Var,Abs) 24
+ 0x05, 0x01, # ..Usage Page (Generic Desktop) 26
+ 0x16, 0x01, 0x80, # ..Logical Minimum (-32767) 28
+ 0x26, 0xff, 0x7f, # ..Logical Maximum (32767) 31
+ 0x75, 0x10, # ..Report Size (16) 34
+ 0x95, 0x02, # ..Report Count (2) 36
+ 0x09, 0x30, # ..Usage (X) 38
+ 0x09, 0x31, # ..Usage (Y) 40
+ 0x81, 0x06, # ..Input (Data,Var,Rel) 42
+ 0x15, 0x81, # ..Logical Minimum (-127) 44
+ 0x25, 0x7f, # ..Logical Maximum (127) 46
+ 0x75, 0x08, # ..Report Size (8) 48
+ 0x95, 0x01, # ..Report Count (1) 50
+ 0x09, 0x38, # ..Usage (Wheel) 52
+ 0x81, 0x06, # ..Input (Data,Var,Rel) 54
+ 0x05, 0x0c, # ..Usage Page (Consumer Devices) 56
+ 0x0a, 0x38, 0x02, # ..Usage (AC Pan) 58
+ 0x95, 0x01, # ..Report Count (1) 61
+ 0x81, 0x06, # ..Input (Data,Var,Rel) 63
+ 0xc0, # .End Collection 65
+ 0xc0, # End Collection 66
+ ]
+ # fmt: on
+
+ def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
+ super().__init__(rdesc, name, input_info)
+ self.hwheel_multiplier = 1
+
+
+class MIDongleMIWirelessMouse(TwoWheelMouse):
+ # fmt: off
+ report_descriptor = [
+ 0x05, 0x01, # Usage Page (Generic Desktop)
+ 0x09, 0x02, # Usage (Mouse)
+ 0xa1, 0x01, # Collection (Application)
+ 0x85, 0x01, # .Report ID (1)
+ 0x09, 0x01, # .Usage (Pointer)
+ 0xa1, 0x00, # .Collection (Physical)
+ 0x95, 0x05, # ..Report Count (5)
+ 0x75, 0x01, # ..Report Size (1)
+ 0x05, 0x09, # ..Usage Page (Button)
+ 0x19, 0x01, # ..Usage Minimum (1)
+ 0x29, 0x05, # ..Usage Maximum (5)
+ 0x15, 0x00, # ..Logical Minimum (0)
+ 0x25, 0x01, # ..Logical Maximum (1)
+ 0x81, 0x02, # ..Input (Data,Var,Abs)
+ 0x95, 0x01, # ..Report Count (1)
+ 0x75, 0x03, # ..Report Size (3)
+ 0x81, 0x01, # ..Input (Cnst,Arr,Abs)
+ 0x75, 0x08, # ..Report Size (8)
+ 0x95, 0x01, # ..Report Count (1)
+ 0x05, 0x01, # ..Usage Page (Generic Desktop)
+ 0x09, 0x38, # ..Usage (Wheel)
+ 0x15, 0x81, # ..Logical Minimum (-127)
+ 0x25, 0x7f, # ..Logical Maximum (127)
+ 0x81, 0x06, # ..Input (Data,Var,Rel)
+ 0x05, 0x0c, # ..Usage Page (Consumer Devices)
+ 0x0a, 0x38, 0x02, # ..Usage (AC Pan)
+ 0x95, 0x01, # ..Report Count (1)
+ 0x81, 0x06, # ..Input (Data,Var,Rel)
+ 0xc0, # .End Collection
+ 0x85, 0x02, # .Report ID (2)
+ 0x09, 0x01, # .Usage (Consumer Control)
+ 0xa1, 0x00, # .Collection (Physical)
+ 0x75, 0x0c, # ..Report Size (12)
+ 0x95, 0x02, # ..Report Count (2)
+ 0x05, 0x01, # ..Usage Page (Generic Desktop)
+ 0x09, 0x30, # ..Usage (X)
+ 0x09, 0x31, # ..Usage (Y)
+ 0x16, 0x01, 0xf8, # ..Logical Minimum (-2047)
+ 0x26, 0xff, 0x07, # ..Logical Maximum (2047)
+ 0x81, 0x06, # ..Input (Data,Var,Rel)
+ 0xc0, # .End Collection
+ 0xc0, # End Collection
+ 0x05, 0x0c, # Usage Page (Consumer Devices)
+ 0x09, 0x01, # Usage (Consumer Control)
+ 0xa1, 0x01, # Collection (Application)
+ 0x85, 0x03, # .Report ID (3)
+ 0x15, 0x00, # .Logical Minimum (0)
+ 0x25, 0x01, # .Logical Maximum (1)
+ 0x75, 0x01, # .Report Size (1)
+ 0x95, 0x01, # .Report Count (1)
+ 0x09, 0xcd, # .Usage (Play/Pause)
+ 0x81, 0x06, # .Input (Data,Var,Rel)
+ 0x0a, 0x83, 0x01, # .Usage (AL Consumer Control Config)
+ 0x81, 0x06, # .Input (Data,Var,Rel)
+ 0x09, 0xb5, # .Usage (Scan Next Track)
+ 0x81, 0x06, # .Input (Data,Var,Rel)
+ 0x09, 0xb6, # .Usage (Scan Previous Track)
+ 0x81, 0x06, # .Input (Data,Var,Rel)
+ 0x09, 0xea, # .Usage (Volume Down)
+ 0x81, 0x06, # .Input (Data,Var,Rel)
+ 0x09, 0xe9, # .Usage (Volume Up)
+ 0x81, 0x06, # .Input (Data,Var,Rel)
+ 0x0a, 0x25, 0x02, # .Usage (AC Forward)
+ 0x81, 0x06, # .Input (Data,Var,Rel)
+ 0x0a, 0x24, 0x02, # .Usage (AC Back)
+ 0x81, 0x06, # .Input (Data,Var,Rel)
+ 0xc0, # End Collection
+ ]
+ # fmt: on
+ device_input_info = (BusType.USB, 0x2717, 0x003B)
+ device_name = "uhid test MI Dongle MI Wireless Mouse"
+
+ def __init__(
+ self, rdesc=report_descriptor, name=device_name, input_info=device_input_info
+ ):
+ super().__init__(rdesc, name, input_info)
+
+ def event(self, x, y, buttons=None, wheels=None):
+ # this mouse spreads the relative pointer and the mouse buttons
+ # onto 2 distinct reports
+ rs = []
+ r = self.create_report(x, y, buttons, wheels, reportID=1)
+ self.call_input_event(r)
+ rs.append(r)
+ r = self.create_report(x, y, buttons, reportID=2)
+ self.call_input_event(r)
+ rs.append(r)
+ return rs
+
+
+class ResolutionMultiplierMouse(TwoWheelMouse):
+ # fmt: off
+ report_descriptor = [
+ 0x05, 0x01, # Usage Page (Generic Desktop) 83
+ 0x09, 0x02, # Usage (Mouse) 85
+ 0xa1, 0x01, # Collection (Application) 87
+ 0x05, 0x01, # .Usage Page (Generic Desktop) 89
+ 0x09, 0x02, # .Usage (Mouse) 91
+ 0xa1, 0x02, # .Collection (Logical) 93
+ 0x85, 0x11, # ..Report ID (17) 95
+ 0x09, 0x01, # ..Usage (Pointer) 97
+ 0xa1, 0x00, # ..Collection (Physical) 99
+ 0x05, 0x09, # ...Usage Page (Button) 101
+ 0x19, 0x01, # ...Usage Minimum (1) 103
+ 0x29, 0x03, # ...Usage Maximum (3) 105
+ 0x95, 0x03, # ...Report Count (3) 107
+ 0x75, 0x01, # ...Report Size (1) 109
+ 0x25, 0x01, # ...Logical Maximum (1) 111
+ 0x81, 0x02, # ...Input (Data,Var,Abs) 113
+ 0x95, 0x01, # ...Report Count (1) 115
+ 0x81, 0x01, # ...Input (Cnst,Arr,Abs) 117
+ 0x09, 0x05, # ...Usage (Vendor Usage 0x05) 119
+ 0x81, 0x02, # ...Input (Data,Var,Abs) 121
+ 0x95, 0x03, # ...Report Count (3) 123
+ 0x81, 0x01, # ...Input (Cnst,Arr,Abs) 125
+ 0x05, 0x01, # ...Usage Page (Generic Desktop) 127
+ 0x09, 0x30, # ...Usage (X) 129
+ 0x09, 0x31, # ...Usage (Y) 131
+ 0x95, 0x02, # ...Report Count (2) 133
+ 0x75, 0x08, # ...Report Size (8) 135
+ 0x15, 0x81, # ...Logical Minimum (-127) 137
+ 0x25, 0x7f, # ...Logical Maximum (127) 139
+ 0x81, 0x06, # ...Input (Data,Var,Rel) 141
+ 0xa1, 0x02, # ...Collection (Logical) 143
+ 0x85, 0x12, # ....Report ID (18) 145
+ 0x09, 0x48, # ....Usage (Resolution Multiplier) 147
+ 0x95, 0x01, # ....Report Count (1) 149
+ 0x75, 0x02, # ....Report Size (2) 151
+ 0x15, 0x00, # ....Logical Minimum (0) 153
+ 0x25, 0x01, # ....Logical Maximum (1) 155
+ 0x35, 0x01, # ....Physical Minimum (1) 157
+ 0x45, 0x04, # ....Physical Maximum (4) 159
+ 0xb1, 0x02, # ....Feature (Data,Var,Abs) 161
+ 0x35, 0x00, # ....Physical Minimum (0) 163
+ 0x45, 0x00, # ....Physical Maximum (0) 165
+ 0x75, 0x06, # ....Report Size (6) 167
+ 0xb1, 0x01, # ....Feature (Cnst,Arr,Abs) 169
+ 0x85, 0x11, # ....Report ID (17) 171
+ 0x09, 0x38, # ....Usage (Wheel) 173
+ 0x15, 0x81, # ....Logical Minimum (-127) 175
+ 0x25, 0x7f, # ....Logical Maximum (127) 177
+ 0x75, 0x08, # ....Report Size (8) 179
+ 0x81, 0x06, # ....Input (Data,Var,Rel) 181
+ 0xc0, # ...End Collection 183
+ 0x05, 0x0c, # ...Usage Page (Consumer Devices) 184
+ 0x75, 0x08, # ...Report Size (8) 186
+ 0x0a, 0x38, 0x02, # ...Usage (AC Pan) 188
+ 0x81, 0x06, # ...Input (Data,Var,Rel) 191
+ 0xc0, # ..End Collection 193
+ 0xc0, # .End Collection 194
+ 0xc0, # End Collection 195
+ ]
+ # fmt: on
+
+ def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
+ super().__init__(rdesc, name, input_info)
+ self.default_reportID = 0x11
+
+ # Feature Report 12, multiplier Feature value must be set to 0b01,
+ # i.e. 1. We should extract that from the descriptor instead
+ # of hardcoding it here, but meanwhile this will do.
+ self.set_feature_report = [0x12, 0x1]
+
+ def set_report(self, req, rnum, rtype, data):
+ if rtype != self.UHID_FEATURE_REPORT:
+ raise InvalidHIDCommunication(f"Unexpected report type: {rtype}")
+ if rnum != 0x12:
+ raise InvalidHIDCommunication(f"Unexpected report number: {rnum}")
+
+ if data != self.set_feature_report:
+ raise InvalidHIDCommunication(
+ f"Unexpected data: {data}, expected {self.set_feature_report}"
+ )
+
+ self.wheel_multiplier = 4
+
+ return 0
+
+
+class BadResolutionMultiplierMouse(ResolutionMultiplierMouse):
+ def set_report(self, req, rnum, rtype, data):
+ super().set_report(req, rnum, rtype, data)
+
+ self.wheel_multiplier = 1
+ self.hwheel_multiplier = 1
+ return 32 # EPIPE
+
+
+class ResolutionMultiplierHWheelMouse(TwoWheelMouse):
+ # fmt: off
+ report_descriptor = [
+ 0x05, 0x01, # Usage Page (Generic Desktop) 0
+ 0x09, 0x02, # Usage (Mouse) 2
+ 0xa1, 0x01, # Collection (Application) 4
+ 0x05, 0x01, # .Usage Page (Generic Desktop) 6
+ 0x09, 0x02, # .Usage (Mouse) 8
+ 0xa1, 0x02, # .Collection (Logical) 10
+ 0x85, 0x1a, # ..Report ID (26) 12
+ 0x09, 0x01, # ..Usage (Pointer) 14
+ 0xa1, 0x00, # ..Collection (Physical) 16
+ 0x05, 0x09, # ...Usage Page (Button) 18
+ 0x19, 0x01, # ...Usage Minimum (1) 20
+ 0x29, 0x05, # ...Usage Maximum (5) 22
+ 0x95, 0x05, # ...Report Count (5) 24
+ 0x75, 0x01, # ...Report Size (1) 26
+ 0x15, 0x00, # ...Logical Minimum (0) 28
+ 0x25, 0x01, # ...Logical Maximum (1) 30
+ 0x81, 0x02, # ...Input (Data,Var,Abs) 32
+ 0x75, 0x03, # ...Report Size (3) 34
+ 0x95, 0x01, # ...Report Count (1) 36
+ 0x81, 0x01, # ...Input (Cnst,Arr,Abs) 38
+ 0x05, 0x01, # ...Usage Page (Generic Desktop) 40
+ 0x09, 0x30, # ...Usage (X) 42
+ 0x09, 0x31, # ...Usage (Y) 44
+ 0x95, 0x02, # ...Report Count (2) 46
+ 0x75, 0x10, # ...Report Size (16) 48
+ 0x16, 0x01, 0x80, # ...Logical Minimum (-32767) 50
+ 0x26, 0xff, 0x7f, # ...Logical Maximum (32767) 53
+ 0x81, 0x06, # ...Input (Data,Var,Rel) 56
+ 0xa1, 0x02, # ...Collection (Logical) 58
+ 0x85, 0x12, # ....Report ID (18) 60
+ 0x09, 0x48, # ....Usage (Resolution Multiplier) 62
+ 0x95, 0x01, # ....Report Count (1) 64
+ 0x75, 0x02, # ....Report Size (2) 66
+ 0x15, 0x00, # ....Logical Minimum (0) 68
+ 0x25, 0x01, # ....Logical Maximum (1) 70
+ 0x35, 0x01, # ....Physical Minimum (1) 72
+ 0x45, 0x0c, # ....Physical Maximum (12) 74
+ 0xb1, 0x02, # ....Feature (Data,Var,Abs) 76
+ 0x85, 0x1a, # ....Report ID (26) 78
+ 0x09, 0x38, # ....Usage (Wheel) 80
+ 0x35, 0x00, # ....Physical Minimum (0) 82
+ 0x45, 0x00, # ....Physical Maximum (0) 84
+ 0x95, 0x01, # ....Report Count (1) 86
+ 0x75, 0x10, # ....Report Size (16) 88
+ 0x16, 0x01, 0x80, # ....Logical Minimum (-32767) 90
+ 0x26, 0xff, 0x7f, # ....Logical Maximum (32767) 93
+ 0x81, 0x06, # ....Input (Data,Var,Rel) 96
+ 0xc0, # ...End Collection 98
+ 0xa1, 0x02, # ...Collection (Logical) 99
+ 0x85, 0x12, # ....Report ID (18) 101
+ 0x09, 0x48, # ....Usage (Resolution Multiplier) 103
+ 0x75, 0x02, # ....Report Size (2) 105
+ 0x15, 0x00, # ....Logical Minimum (0) 107
+ 0x25, 0x01, # ....Logical Maximum (1) 109
+ 0x35, 0x01, # ....Physical Minimum (1) 111
+ 0x45, 0x0c, # ....Physical Maximum (12) 113
+ 0xb1, 0x02, # ....Feature (Data,Var,Abs) 115
+ 0x35, 0x00, # ....Physical Minimum (0) 117
+ 0x45, 0x00, # ....Physical Maximum (0) 119
+ 0x75, 0x04, # ....Report Size (4) 121
+ 0xb1, 0x01, # ....Feature (Cnst,Arr,Abs) 123
+ 0x85, 0x1a, # ....Report ID (26) 125
+ 0x05, 0x0c, # ....Usage Page (Consumer Devices) 127
+ 0x95, 0x01, # ....Report Count (1) 129
+ 0x75, 0x10, # ....Report Size (16) 131
+ 0x16, 0x01, 0x80, # ....Logical Minimum (-32767) 133
+ 0x26, 0xff, 0x7f, # ....Logical Maximum (32767) 136
+ 0x0a, 0x38, 0x02, # ....Usage (AC Pan) 139
+ 0x81, 0x06, # ....Input (Data,Var,Rel) 142
+ 0xc0, # ...End Collection 144
+ 0xc0, # ..End Collection 145
+ 0xc0, # .End Collection 146
+ 0xc0, # End Collection 147
+ ]
+ # fmt: on
+
+ def __init__(self, rdesc=report_descriptor, name=None, input_info=None):
+ super().__init__(rdesc, name, input_info)
+ self.default_reportID = 0x1A
+
+ # Feature Report 12, multiplier Feature value must be set to 0b0101,
+ # i.e. 5. We should extract that from the descriptor instead
+ # of hardcoding it here, but meanwhile this will do.
+ self.set_feature_report = [0x12, 0x5]
+
+ def set_report(self, req, rnum, rtype, data):
+ super().set_report(req, rnum, rtype, data)
+
+ self.wheel_multiplier = 12
+ self.hwheel_multiplier = 12
+
+ return 0
+
+
+class BaseTest:
+ class TestMouse(base.BaseTestCase.TestUhid):
+ def test_buttons(self):
+ """check for button reliability."""
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+ syn_event = self.syn_event
+
+ r = uhdev.event(0, 0, (None, True, None))
+ expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 1)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn((syn_event, expected_event), events)
+ assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 1
+
+ r = uhdev.event(0, 0, (None, False, None))
+ expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 0)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn((syn_event, expected_event), events)
+ assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 0
+
+ r = uhdev.event(0, 0, (None, None, True))
+ expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_MIDDLE, 1)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn((syn_event, expected_event), events)
+ assert evdev.value[libevdev.EV_KEY.BTN_MIDDLE] == 1
+
+ r = uhdev.event(0, 0, (None, None, False))
+ expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_MIDDLE, 0)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn((syn_event, expected_event), events)
+ assert evdev.value[libevdev.EV_KEY.BTN_MIDDLE] == 0
+
+ r = uhdev.event(0, 0, (True, None, None))
+ expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 1)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn((syn_event, expected_event), events)
+ assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 1
+
+ r = uhdev.event(0, 0, (False, None, None))
+ expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 0)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn((syn_event, expected_event), events)
+ assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0
+
+ r = uhdev.event(0, 0, (True, True, None))
+ expected_event0 = libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 1)
+ expected_event1 = libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 1)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn(
+ (syn_event, expected_event0, expected_event1), events
+ )
+ assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 1
+ assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 1
+
+ r = uhdev.event(0, 0, (False, None, None))
+ expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 0)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn((syn_event, expected_event), events)
+ assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 1
+ assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0
+
+ r = uhdev.event(0, 0, (None, False, None))
+ expected_event = libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 0)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEventsIn((syn_event, expected_event), events)
+ assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 0
+ assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0
+
+ def test_relative(self):
+ """Check for relative events."""
+ uhdev = self.uhdev
+
+ syn_event = self.syn_event
+
+ r = uhdev.event(0, -1)
+ expected_event = libevdev.InputEvent(libevdev.EV_REL.REL_Y, -1)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEvents((syn_event, expected_event), events)
+
+ r = uhdev.event(1, 0)
+ expected_event = libevdev.InputEvent(libevdev.EV_REL.REL_X, 1)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEvents((syn_event, expected_event), events)
+
+ r = uhdev.event(-1, 2)
+ expected_event0 = libevdev.InputEvent(libevdev.EV_REL.REL_X, -1)
+ expected_event1 = libevdev.InputEvent(libevdev.EV_REL.REL_Y, 2)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEvents(
+ (syn_event, expected_event0, expected_event1), events
+ )
+
+
+class TestSimpleMouse(BaseTest.TestMouse):
+ def create_device(self):
+ return ButtonMouse()
+
+ def test_rdesc(self):
+ """Check that the testsuite actually manages to format the
+ reports according to the report descriptors.
+ No kernel device is used here"""
+ uhdev = self.uhdev
+
+ event = (0, 0, (None, None, None))
+ assert uhdev.fake_report(*event) == uhdev.create_report(*event)
+
+ event = (0, 0, (None, True, None))
+ assert uhdev.fake_report(*event) == uhdev.create_report(*event)
+
+ event = (0, 0, (True, True, None))
+ assert uhdev.fake_report(*event) == uhdev.create_report(*event)
+
+ event = (0, 0, (False, False, False))
+ assert uhdev.fake_report(*event) == uhdev.create_report(*event)
+
+ event = (1, 0, (True, False, True))
+ assert uhdev.fake_report(*event) == uhdev.create_report(*event)
+
+ event = (-1, 0, (True, False, True))
+ assert uhdev.fake_report(*event) == uhdev.create_report(*event)
+
+ event = (-5, 5, (True, False, True))
+ assert uhdev.fake_report(*event) == uhdev.create_report(*event)
+
+ event = (-127, 127, (True, False, True))
+ assert uhdev.fake_report(*event) == uhdev.create_report(*event)
+
+ event = (0, -128, (True, False, True))
+ with pytest.raises(hidtools.hid.RangeError):
+ uhdev.create_report(*event)
+
+
+class TestWheelMouse(BaseTest.TestMouse):
+ def create_device(self):
+ return WheelMouse()
+
+ def is_wheel_highres(self, uhdev):
+ evdev = uhdev.get_evdev()
+ assert evdev.has(libevdev.EV_REL.REL_WHEEL)
+ return evdev.has(libevdev.EV_REL.REL_WHEEL_HI_RES)
+
+ def test_wheel(self):
+ uhdev = self.uhdev
+
+ # check if the kernel is high res wheel compatible
+ high_res_wheel = self.is_wheel_highres(uhdev)
+
+ syn_event = self.syn_event
+ # The Resolution Multiplier is applied to the HID reports, so we
+ # need to pre-multiply too.
+ mult = uhdev.wheel_multiplier
+
+ r = uhdev.event(0, 0, wheels=1 * mult)
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, 1))
+ if high_res_wheel:
+ expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, 120))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEvents(expected, events)
+
+ r = uhdev.event(0, 0, wheels=-1 * mult)
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, -1))
+ if high_res_wheel:
+ expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, -120))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEvents(expected, events)
+
+ r = uhdev.event(-1, 2, wheels=3 * mult)
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, -1))
+ expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, 2))
+ expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, 3))
+ if high_res_wheel:
+ expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, 360))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEvents(expected, events)
+
+
+class TestTwoWheelMouse(TestWheelMouse):
+ def create_device(self):
+ return TwoWheelMouse()
+
+ def is_hwheel_highres(self, uhdev):
+ evdev = uhdev.get_evdev()
+ assert evdev.has(libevdev.EV_REL.REL_HWHEEL)
+ return evdev.has(libevdev.EV_REL.REL_HWHEEL_HI_RES)
+
+ def test_ac_pan(self):
+ uhdev = self.uhdev
+
+ # check if the kernel is high res wheel compatible
+ high_res_wheel = self.is_wheel_highres(uhdev)
+ high_res_hwheel = self.is_hwheel_highres(uhdev)
+ assert high_res_wheel == high_res_hwheel
+
+ syn_event = self.syn_event
+ # The Resolution Multiplier is applied to the HID reports, so we
+ # need to pre-multiply too.
+ hmult = uhdev.hwheel_multiplier
+ vmult = uhdev.wheel_multiplier
+
+ r = uhdev.event(0, 0, wheels=(0, 1 * hmult))
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, 1))
+ if high_res_hwheel:
+ expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 120))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEvents(expected, events)
+
+ r = uhdev.event(0, 0, wheels=(0, -1 * hmult))
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, -1))
+ if high_res_hwheel:
+ expected.append(
+ libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, -120)
+ )
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEvents(expected, events)
+
+ r = uhdev.event(-1, 2, wheels=(0, 3 * hmult))
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, -1))
+ expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, 2))
+ expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, 3))
+ if high_res_hwheel:
+ expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 360))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEvents(expected, events)
+
+ r = uhdev.event(-1, 2, wheels=(-3 * vmult, 4 * hmult))
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, -1))
+ expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, 2))
+ expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, -3))
+ if high_res_wheel:
+ expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, -360))
+ expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, 4))
+ if high_res_wheel:
+ expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 480))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEvents(expected, events)
+
+
+class TestResolutionMultiplierMouse(TestTwoWheelMouse):
+ def create_device(self):
+ return ResolutionMultiplierMouse()
+
+ def is_wheel_highres(self, uhdev):
+ high_res = super().is_wheel_highres(uhdev)
+
+ if not high_res:
+ # the kernel doesn't seem to support the high res wheel mice,
+ # make sure we haven't triggered the feature
+ assert uhdev.wheel_multiplier == 1
+
+ return high_res
+
+ def test_resolution_multiplier_wheel(self):
+ uhdev = self.uhdev
+
+ if not self.is_wheel_highres(uhdev):
+ pytest.skip("Kernel not compatible, we can not trigger the conditions")
+
+ assert uhdev.wheel_multiplier > 1
+ assert 120 % uhdev.wheel_multiplier == 0
+
+ def test_wheel_with_multiplier(self):
+ uhdev = self.uhdev
+
+ if not self.is_wheel_highres(uhdev):
+ pytest.skip("Kernel not compatible, we can not trigger the conditions")
+
+ assert uhdev.wheel_multiplier > 1
+
+ syn_event = self.syn_event
+ mult = uhdev.wheel_multiplier
+
+ r = uhdev.event(0, 0, wheels=1)
+ expected = [syn_event]
+ expected.append(
+ libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, 120 / mult)
+ )
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEvents(expected, events)
+
+ r = uhdev.event(0, 0, wheels=-1)
+ expected = [syn_event]
+ expected.append(
+ libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, -120 / mult)
+ )
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEvents(expected, events)
+
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, 1))
+ expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, -2))
+ expected.append(
+ libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL_HI_RES, 120 / mult)
+ )
+
+ for _ in range(mult - 1):
+ r = uhdev.event(1, -2, wheels=1)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEvents(expected, events)
+
+ r = uhdev.event(1, -2, wheels=1)
+ expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_WHEEL, 1))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEvents(expected, events)
+
+
+class TestBadResolutionMultiplierMouse(TestTwoWheelMouse):
+ def create_device(self):
+ return BadResolutionMultiplierMouse()
+
+ def is_wheel_highres(self, uhdev):
+ high_res = super().is_wheel_highres(uhdev)
+
+ assert uhdev.wheel_multiplier == 1
+
+ return high_res
+
+ def test_resolution_multiplier_wheel(self):
+ uhdev = self.uhdev
+
+ assert uhdev.wheel_multiplier == 1
+
+
+class TestResolutionMultiplierHWheelMouse(TestResolutionMultiplierMouse):
+ def create_device(self):
+ return ResolutionMultiplierHWheelMouse()
+
+ def is_hwheel_highres(self, uhdev):
+ high_res = super().is_hwheel_highres(uhdev)
+
+ if not high_res:
+ # the kernel doesn't seem to support the high res wheel mice,
+ # make sure we haven't triggered the feature
+ assert uhdev.hwheel_multiplier == 1
+
+ return high_res
+
+ def test_resolution_multiplier_ac_pan(self):
+ uhdev = self.uhdev
+
+ if not self.is_hwheel_highres(uhdev):
+ pytest.skip("Kernel not compatible, we can not trigger the conditions")
+
+ assert uhdev.hwheel_multiplier > 1
+ assert 120 % uhdev.hwheel_multiplier == 0
+
+ def test_ac_pan_with_multiplier(self):
+ uhdev = self.uhdev
+
+ if not self.is_hwheel_highres(uhdev):
+ pytest.skip("Kernel not compatible, we can not trigger the conditions")
+
+ assert uhdev.hwheel_multiplier > 1
+
+ syn_event = self.syn_event
+ hmult = uhdev.hwheel_multiplier
+
+ r = uhdev.event(0, 0, wheels=(0, 1))
+ expected = [syn_event]
+ expected.append(
+ libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 120 / hmult)
+ )
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEvents(expected, events)
+
+ r = uhdev.event(0, 0, wheels=(0, -1))
+ expected = [syn_event]
+ expected.append(
+ libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, -120 / hmult)
+ )
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEvents(expected, events)
+
+ expected = [syn_event]
+ expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_X, 1))
+ expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_Y, -2))
+ expected.append(
+ libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL_HI_RES, 120 / hmult)
+ )
+
+ for _ in range(hmult - 1):
+ r = uhdev.event(1, -2, wheels=(0, 1))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEvents(expected, events)
+
+ r = uhdev.event(1, -2, wheels=(0, 1))
+ expected.append(libevdev.InputEvent(libevdev.EV_REL.REL_HWHEEL, 1))
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ self.assertInputEvents(expected, events)
+
+
+class TestMiMouse(TestWheelMouse):
+ def create_device(self):
+ return MIDongleMIWirelessMouse()
+
+ def assertInputEvents(self, expected_events, effective_events):
+ # Buttons and x/y are spread over two HID reports, so we can get two
+ # event frames for this device.
+ remaining = self.assertInputEventsIn(expected_events, effective_events)
+ try:
+ remaining.remove(libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT, 0))
+ except ValueError:
+ # If there's no SYN_REPORT in the list, continue and let the
+ # assert below print out the real error
+ pass
+ assert remaining == []
diff --git a/tools/testing/selftests/hid/tests/test_multitouch.py b/tools/testing/selftests/hid/tests/test_multitouch.py
new file mode 100644
index 000000000000..4265012231c6
--- /dev/null
+++ b/tools/testing/selftests/hid/tests/test_multitouch.py
@@ -0,0 +1,2088 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2017 Red Hat, Inc.
+#
+
+from . import base
+from hidtools.hut import HUT
+from hidtools.util import BusType
+import libevdev
+import logging
+import pytest
+import sys
+import time
+
+logger = logging.getLogger("hidtools.test.multitouch")
+
+KERNEL_MODULE = ("hid-multitouch", "hid_multitouch")
+
+
+def BIT(x):
+ return 1 << x
+
+
+mt_quirks = {
+ "NOT_SEEN_MEANS_UP": BIT(0),
+ "SLOT_IS_CONTACTID": BIT(1),
+ "CYPRESS": BIT(2),
+ "SLOT_IS_CONTACTNUMBER": BIT(3),
+ "ALWAYS_VALID": BIT(4),
+ "VALID_IS_INRANGE": BIT(5),
+ "VALID_IS_CONFIDENCE": BIT(6),
+ "CONFIDENCE": BIT(7),
+ "SLOT_IS_CONTACTID_MINUS_ONE": BIT(8),
+ "NO_AREA": BIT(9),
+ "IGNORE_DUPLICATES": BIT(10),
+ "HOVERING": BIT(11),
+ "CONTACT_CNT_ACCURATE": BIT(12),
+ "FORCE_GET_FEATURE": BIT(13),
+ "FIX_CONST_CONTACT_ID": BIT(14),
+ "TOUCH_SIZE_SCALING": BIT(15),
+ "STICKY_FINGERS": BIT(16),
+ "ASUS_CUSTOM_UP": BIT(17),
+ "WIN8_PTP_BUTTONS": BIT(18),
+ "SEPARATE_APP_REPORT": BIT(19),
+ "MT_QUIRK_FORCE_MULTI_INPUT": BIT(20),
+}
+
+
+class Data(object):
+ pass
+
+
+class Touch(object):
+ def __init__(self, id, x, y):
+ self.contactid = id
+ self.x = x
+ self.y = y
+ self.cx = x
+ self.cy = y
+ self.tipswitch = True
+ self.confidence = True
+ self.tippressure = 15
+ self.azimuth = 0
+ self.inrange = True
+ self.width = 10
+ self.height = 10
+
+
+class Pen(Touch):
+ def __init__(self, x, y):
+ super().__init__(0, x, y)
+ self.barrel = False
+ self.invert = False
+ self.eraser = False
+ self.x_tilt = False
+ self.y_tilt = False
+ self.twist = 0
+
+
+class Digitizer(base.UHIDTestDevice):
+ @classmethod
+ def msCertificationBlob(cls, reportID):
+ return f"""
+ Usage Page (Digitizers)
+ Usage (Touch Screen)
+ Collection (Application)
+ Report ID ({reportID})
+ Usage Page (0xff00)
+ Usage (0xc5)
+ Logical Minimum (0)
+ Logical Maximum (255)
+ Report Size (8)
+ Report Count (256)
+ Feature (Data,Var,Abs)
+ End Collection
+ """
+
+ def __init__(
+ self,
+ name,
+ rdesc_str=None,
+ rdesc=None,
+ application="Touch Screen",
+ physical="Finger",
+ max_contacts=None,
+ input_info=(BusType.USB, 1, 2),
+ quirks=None,
+ ):
+ super().__init__(name, application, rdesc_str, rdesc, input_info)
+ self.scantime = 0
+ self.quirks = quirks
+ if max_contacts is None:
+ self.max_contacts = sys.maxsize
+ for features in self.parsed_rdesc.feature_reports.values():
+ for feature in features:
+ if feature.usage_name in ["Contact Max"]:
+ self.max_contacts = feature.logical_max
+ for inputs in self.parsed_rdesc.input_reports.values():
+ for i in inputs:
+ if (
+ i.usage_name in ["Contact Count"]
+ and i.logical_max > 0
+ and self.max_contacts > i.logical_max
+ ):
+ self.max_contacts = i.logical_max
+ if self.max_contacts == sys.maxsize:
+ self.max_contacts = 1
+ else:
+ self.max_contacts = max_contacts
+ self.physical = physical
+ self.cur_application = application
+
+ for features in self.parsed_rdesc.feature_reports.values():
+ for feature in features:
+ if feature.usage_name == "Inputmode":
+ self.cur_application = "Mouse"
+
+ self.fields = []
+ for r in self.parsed_rdesc.input_reports.values():
+ if r.application_name == self.application:
+ physicals = [f.physical_name for f in r]
+ if self.physical not in physicals and None not in physicals:
+ continue
+ self.fields = [f.usage_name for f in r]
+
+ @property
+ def touches_in_a_report(self):
+ return self.fields.count("Contact Id")
+
+ def event(self, slots, global_data=None, contact_count=None, incr_scantime=True):
+ if incr_scantime:
+ self.scantime += 1
+ rs = []
+ # make sure we have only the required number of available slots
+ slots = slots[: self.max_contacts]
+
+ if global_data is None:
+ global_data = Data()
+ if contact_count is None:
+ global_data.contactcount = len(slots)
+ else:
+ global_data.contactcount = contact_count
+ global_data.scantime = self.scantime
+
+ while len(slots):
+ r = self.create_report(
+ application=self.cur_application, data=slots, global_data=global_data
+ )
+ self.call_input_event(r)
+ rs.append(r)
+ global_data.contactcount = 0
+ return rs
+
+ def get_report(self, req, rnum, rtype):
+ if rtype != self.UHID_FEATURE_REPORT:
+ return (1, [])
+
+ rdesc = None
+ for v in self.parsed_rdesc.feature_reports.values():
+ if v.report_ID == rnum:
+ rdesc = v
+
+ if rdesc is None:
+ return (1, [])
+
+ if "Contact Max" not in [f.usage_name for f in rdesc]:
+ return (1, [])
+
+ self.contactmax = self.max_contacts
+ r = rdesc.create_report([self], None)
+ return (0, r)
+
+ def set_report(self, req, rnum, rtype, data):
+ if rtype != self.UHID_FEATURE_REPORT:
+ return 1
+
+ rdesc = None
+ for v in self.parsed_rdesc.feature_reports.values():
+ if v.report_ID == rnum:
+ rdesc = v
+
+ if rdesc is None:
+ return 1
+
+ if "Inputmode" not in [f.usage_name for f in rdesc]:
+ return 0
+
+ Inputmode_seen = False
+ for f in rdesc:
+ if "Inputmode" == f.usage_name:
+ values = f.get_values(data)
+ assert len(values) == 1
+ value = values[0]
+
+ if not Inputmode_seen:
+ Inputmode_seen = True
+ if value == 0:
+ self.cur_application = "Mouse"
+ elif value == 2:
+ self.cur_application = "Touch Screen"
+ elif value == 3:
+ self.cur_application = "Touch Pad"
+ else:
+ if value != 0:
+ # Elan bug where the device doesn't work properly
+ # if we set twice an Input Mode in the same Feature
+ self.cur_application = "Mouse"
+
+ return 0
+
+
+class PTP(Digitizer):
+ def __init__(
+ self,
+ name,
+ type="Click Pad",
+ rdesc_str=None,
+ rdesc=None,
+ application="Touch Pad",
+ physical="Pointer",
+ max_contacts=None,
+ input_info=None,
+ ):
+ self.type = type.lower().replace(" ", "")
+ if self.type == "clickpad":
+ self.buttontype = 0
+ else: # pressurepad
+ self.buttontype = 1
+ self.clickpad_state = False
+ self.left_state = False
+ self.right_state = False
+ super().__init__(
+ name, rdesc_str, rdesc, application, physical, max_contacts, input_info
+ )
+
+ def event(
+ self,
+ slots=None,
+ click=None,
+ left=None,
+ right=None,
+ contact_count=None,
+ incr_scantime=True,
+ ):
+ # update our internal state
+ if click is not None:
+ self.clickpad_state = click
+ if left is not None:
+ self.left_state = left
+ if right is not None:
+ self.right_state = right
+
+ # now create the global data
+ global_data = Data()
+ global_data.b1 = 1 if self.clickpad_state else 0
+ global_data.b2 = 1 if self.left_state else 0
+ global_data.b3 = 1 if self.right_state else 0
+
+ if slots is None:
+ slots = [Data()]
+
+ return super().event(slots, global_data, contact_count, incr_scantime)
+
+
+class MinWin8TSParallel(Digitizer):
+ def __init__(self, max_slots):
+ self.max_slots = max_slots
+ self.phys_max = 120, 90
+ rdesc_finger_str = f"""
+ Usage Page (Digitizers)
+ Usage (Finger)
+ Collection (Logical)
+ Report Size (1)
+ Report Count (1)
+ Logical Minimum (0)
+ Logical Maximum (1)
+ Usage (Tip Switch)
+ Input (Data,Var,Abs)
+ Report Size (7)
+ Logical Maximum (127)
+ Input (Cnst,Var,Abs)
+ Report Size (8)
+ Logical Maximum (255)
+ Usage (Contact Id)
+ Input (Data,Var,Abs)
+ Report Size (16)
+ Unit Exponent (-1)
+ Unit (SILinear: cm)
+ Logical Maximum (4095)
+ Physical Minimum (0)
+ Physical Maximum ({self.phys_max[0]})
+ Usage Page (Generic Desktop)
+ Usage (X)
+ Input (Data,Var,Abs)
+ Physical Maximum ({self.phys_max[1]})
+ Usage (Y)
+ Input (Data,Var,Abs)
+ Usage Page (Digitizers)
+ Usage (Azimuth)
+ Logical Maximum (360)
+ Unit (SILinear: deg)
+ Report Size (16)
+ Input (Data,Var,Abs)
+ End Collection
+"""
+ rdesc_str = f"""
+ Usage Page (Digitizers)
+ Usage (Touch Screen)
+ Collection (Application)
+ Report ID (1)
+ {rdesc_finger_str * self.max_slots}
+ Unit Exponent (-4)
+ Unit (SILinear: s)
+ Logical Maximum (65535)
+ Physical Maximum (65535)
+ Usage Page (Digitizers)
+ Usage (Scan Time)
+ Input (Data,Var,Abs)
+ Report Size (8)
+ Logical Maximum (255)
+ Usage (Contact Count)
+ Input (Data,Var,Abs)
+ Report ID (2)
+ Logical Maximum ({self.max_slots})
+ Usage (Contact Max)
+ Feature (Data,Var,Abs)
+ End Collection
+ {Digitizer.msCertificationBlob(68)}
+"""
+ super().__init__(f"uhid test parallel {self.max_slots}", rdesc_str)
+
+
+class MinWin8TSHybrid(Digitizer):
+ def __init__(self):
+ self.max_slots = 10
+ self.phys_max = 120, 90
+ rdesc_finger_str = f"""
+ Usage Page (Digitizers)
+ Usage (Finger)
+ Collection (Logical)
+ Report Size (1)
+ Report Count (1)
+ Logical Minimum (0)
+ Logical Maximum (1)
+ Usage (Tip Switch)
+ Input (Data,Var,Abs)
+ Report Size (7)
+ Logical Maximum (127)
+ Input (Cnst,Var,Abs)
+ Report Size (8)
+ Logical Maximum (255)
+ Usage (Contact Id)
+ Input (Data,Var,Abs)
+ Report Size (16)
+ Unit Exponent (-1)
+ Unit (SILinear: cm)
+ Logical Maximum (4095)
+ Physical Minimum (0)
+ Physical Maximum ({self.phys_max[0]})
+ Usage Page (Generic Desktop)
+ Usage (X)
+ Input (Data,Var,Abs)
+ Physical Maximum ({self.phys_max[1]})
+ Usage (Y)
+ Input (Data,Var,Abs)
+ End Collection
+"""
+ rdesc_str = f"""
+ Usage Page (Digitizers)
+ Usage (Touch Screen)
+ Collection (Application)
+ Report ID (1)
+ {rdesc_finger_str * 2}
+ Unit Exponent (-4)
+ Unit (SILinear: s)
+ Logical Maximum (65535)
+ Physical Maximum (65535)
+ Usage Page (Digitizers)
+ Usage (Scan Time)
+ Input (Data,Var,Abs)
+ Report Size (8)
+ Logical Maximum (255)
+ Usage (Contact Count)
+ Input (Data,Var,Abs)
+ Report ID (2)
+ Logical Maximum ({self.max_slots})
+ Usage (Contact Max)
+ Feature (Data,Var,Abs)
+ End Collection
+ {Digitizer.msCertificationBlob(68)}
+"""
+ super().__init__("uhid test hybrid", rdesc_str)
+
+
+class Win8TSConfidence(Digitizer):
+ def __init__(self, max_slots):
+ self.max_slots = max_slots
+ self.phys_max = 120, 90
+ rdesc_finger_str = f"""
+ Usage Page (Digitizers)
+ Usage (Finger)
+ Collection (Logical)
+ Report Size (1)
+ Report Count (1)
+ Logical Minimum (0)
+ Logical Maximum (1)
+ Usage (Tip Switch)
+ Input (Data,Var,Abs)
+ Usage (Confidence)
+ Input (Data,Var,Abs)
+ Report Size (6)
+ Logical Maximum (127)
+ Input (Cnst,Var,Abs)
+ Report Size (8)
+ Logical Maximum (255)
+ Usage (Contact Id)
+ Input (Data,Var,Abs)
+ Report Size (16)
+ Unit Exponent (-1)
+ Unit (SILinear: cm)
+ Logical Maximum (4095)
+ Physical Minimum (0)
+ Physical Maximum ({self.phys_max[0]})
+ Usage Page (Generic Desktop)
+ Usage (X)
+ Input (Data,Var,Abs)
+ Physical Maximum ({self.phys_max[1]})
+ Usage (Y)
+ Input (Data,Var,Abs)
+ Usage Page (Digitizers)
+ Usage (Azimuth)
+ Logical Maximum (360)
+ Unit (SILinear: deg)
+ Report Size (16)
+ Input (Data,Var,Abs)
+ End Collection
+"""
+ rdesc_str = f"""
+ Usage Page (Digitizers)
+ Usage (Touch Screen)
+ Collection (Application)
+ Report ID (1)
+ {rdesc_finger_str * self.max_slots}
+ Unit Exponent (-4)
+ Unit (SILinear: s)
+ Logical Maximum (65535)
+ Physical Maximum (65535)
+ Usage Page (Digitizers)
+ Usage (Scan Time)
+ Input (Data,Var,Abs)
+ Report Size (8)
+ Logical Maximum (255)
+ Usage (Contact Count)
+ Input (Data,Var,Abs)
+ Report ID (2)
+ Logical Maximum ({self.max_slots})
+ Usage (Contact Max)
+ Feature (Data,Var,Abs)
+ End Collection
+ {Digitizer.msCertificationBlob(68)}
+"""
+ super().__init__(f"uhid test confidence {self.max_slots}", rdesc_str)
+
+
+class SmartTechDigitizer(Digitizer):
+ def __init__(self, name, input_info):
+ super().__init__(
+ name,
+ rdesc="05 01 09 02 a1 01 85 01 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 95 03 75 01 81 02 95 05 81 03 05 01 15 00 26 ff 0f 55 0e 65 11 75 10 95 01 35 00 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 c0 05 0d 09 06 15 00 26 ff 00 a1 01 85 02 75 08 95 3f 09 00 82 02 01 95 3f 09 00 92 02 01 c0 05 0d 09 04 a1 01 85 05 05 0d 09 20 a1 00 25 01 75 01 95 02 09 42 09 45 81 02 75 06 95 01 09 30 81 02 26 ff 00 75 08 09 51 81 02 75 10 09 38 81 02 95 02 26 ff 0f 09 48 09 49 81 02 05 01 09 30 09 31 81 02 c0 05 0d 09 20 a1 00 25 01 75 01 95 02 09 42 09 45 81 02 75 06 95 01 09 30 81 02 26 ff 00 75 08 09 51 81 02 75 10 09 38 81 02 95 02 26 ff 0f 09 48 09 49 81 02 05 01 09 30 09 31 81 02 c0 05 0d 09 20 a1 00 25 01 75 01 95 02 09 42 09 45 81 02 75 06 95 01 09 30 81 02 26 ff 00 75 08 09 51 81 02 75 10 09 38 81 02 95 02 26 ff 0f 09 48 09 49 81 02 05 01 09 30 09 31 81 02 c0 05 0d 09 20 a1 00 25 01 75 01 95 02 09 42 09 45 81 02 75 06 95 01 09 30 81 02 26 ff 00 75 08 09 51 81 02 75 10 09 38 81 02 95 02 26 ff 0f 09 48 09 49 81 02 05 01 09 30 09 31 81 02 c0 05 0d 75 08 95 01 15 00 25 0a 09 54 81 02 09 55 b1 02 c0 05 0d 09 0e a1 01 85 04 09 23 a1 02 15 00 25 02 75 08 95 02 09 52 09 53 b1 02 c0 c0 05 0d 09 04 a1 01 85 03 05 0d 09 22 a1 02 15 00 25 01 75 01 95 02 09 42 09 47 81 02 95 02 81 03 75 04 95 01 25 0f 09 30 81 02 26 ff 00 75 08 95 01 09 51 81 02 75 10 27 a0 8c 00 00 55 0e 65 14 47 a0 8c 00 00 09 3f 81 02 65 11 26 ff 0f 46 c8 37 09 48 81 02 46 68 1f 09 49 81 02 05 01 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 15 00 25 01 75 01 95 02 09 42 09 47 81 02 95 02 81 03 75 04 95 01 25 0f 09 30 81 02 26 ff 00 75 08 95 01 09 51 81 02 75 10 27 a0 8c 00 00 55 0e 65 14 47 a0 8c 00 00 09 3f 81 02 65 11 26 ff 0f 46 c8 37 09 48 81 02 46 68 1f 09 49 81 02 05 01 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 15 00 25 01 75 01 95 02 09 42 09 47 81 02 95 02 81 03 75 04 95 01 25 0f 09 30 81 02 26 ff 00 75 08 95 01 09 51 81 02 75 10 27 a0 8c 00 00 55 0e 65 14 47 a0 8c 00 00 09 3f 81 02 65 11 26 ff 0f 46 c8 37 09 48 81 02 46 68 1f 09 49 81 02 05 01 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 15 00 25 01 75 01 95 02 09 42 09 47 81 02 95 02 81 03 75 04 95 01 25 0f 09 30 81 02 26 ff 00 75 08 95 01 09 51 81 02 75 10 27 a0 8c 00 00 55 0e 65 14 47 a0 8c 00 00 09 3f 81 02 65 11 26 ff 0f 46 c8 37 09 48 81 02 46 68 1f 09 49 81 02 05 01 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 05 0d 75 08 95 01 15 00 25 0a 09 54 81 02 09 55 b1 02 c0 05 0d 09 04 a1 01 85 06 09 22 a1 02 15 00 25 01 75 01 95 02 09 42 09 47 81 02 95 06 81 03 95 01 75 10 65 11 55 0e 26 ff 0f 46 c8 37 09 48 81 02 46 68 1f 09 49 81 02 05 01 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 c0 05 0d 09 02 a1 01 85 07 09 20 a1 02 25 01 75 01 95 04 09 42 09 44 09 3c 09 45 81 02 75 04 95 01 25 0f 09 30 81 02 26 ff 00 75 08 09 38 81 02 75 10 27 a0 8c 00 00 55 0e 65 14 47 a0 8c 00 00 09 3f 81 02 65 11 26 ff 0f 46 c8 37 09 48 81 02 46 68 1f 09 49 81 02 05 01 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 c0 05 0d 09 02 a1 01 85 08 09 20 a1 02 25 01 75 01 95 04 09 42 09 44 09 3c 09 45 81 02 75 04 95 01 25 0f 09 30 81 02 26 ff 00 75 08 09 38 81 02 75 10 27 a0 8c 00 00 55 0e 65 14 47 a0 8c 00 00 09 3f 81 02 65 11 26 ff 0f 46 c8 37 09 48 81 02 46 68 1f 09 49 81 02 05 01 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 c0 05 0d 09 02 a1 01 85 09 09 20 a1 02 25 01 75 01 95 04 09 42 09 44 09 3c 09 45 81 02 75 04 95 01 25 0f 09 30 81 02 26 ff 00 75 08 09 38 81 02 75 10 27 a0 8c 00 00 55 0e 65 14 47 a0 8c 00 00 09 3f 81 02 65 11 26 ff 0f 46 c8 37 09 48 81 02 46 68 1f 09 49 81 02 05 01 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 c0 05 0d 09 02 a1 01 85 0a 09 20 a1 02 25 01 75 01 95 04 09 42 09 44 09 3c 09 45 81 02 75 04 95 01 25 0f 09 30 81 02 26 ff 00 75 08 09 38 81 02 75 10 27 a0 8c 00 00 55 0e 65 14 47 a0 8c 00 00 09 3f 81 02 65 11 26 ff 0f 46 c8 37 09 48 81 02 46 68 1f 09 49 81 02 05 01 46 c8 37 09 30 81 02 46 68 1f 09 31 81 02 45 00 c0 c0",
+ input_info=input_info,
+ )
+
+ def create_report(self, data, global_data=None, reportID=None, application=None):
+ # this device has *a lot* of different reports, and most of them
+ # have the Touch Screen application. But the first one is a stylus
+ # report (as stated in the physical type), so we simply override
+ # the report ID to use what the device sends
+ return super().create_report(data, global_data=global_data, reportID=3)
+
+ def match_evdev_rule(self, application, evdev):
+ # we need to select the correct evdev node, as the device has multiple
+ # Touch Screen application collections
+ if application != "Touch Screen":
+ return True
+ absinfo = evdev.absinfo[libevdev.EV_ABS.ABS_MT_POSITION_X]
+ return absinfo is not None and absinfo.resolution == 3
+
+
+class BaseTest:
+ class TestMultitouch(base.BaseTestCase.TestUhid):
+ kernel_modules = [KERNEL_MODULE]
+
+ def create_device(self):
+ raise Exception("please reimplement me in subclasses")
+
+ def get_slot(self, uhdev, t, default):
+ if uhdev.quirks is None:
+ return default
+
+ if "SLOT_IS_CONTACTID" in uhdev.quirks:
+ return t.contactid
+
+ if "SLOT_IS_CONTACTID_MINUS_ONE" in uhdev.quirks:
+ return t.contactid - 1
+
+ return default
+
+ def test_creation(self):
+ """Make sure the device gets processed by the kernel and creates
+ the expected application input node.
+
+ If this fail, there is something wrong in the device report
+ descriptors."""
+ super().test_creation()
+
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+
+ # some sanity checking for the quirks
+ if uhdev.quirks is not None:
+ for q in uhdev.quirks:
+ assert q in mt_quirks
+
+ assert evdev.num_slots == uhdev.max_contacts
+
+ if uhdev.max_contacts > 1:
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+ assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+ if uhdev.max_contacts > 2:
+ assert evdev.slots[2][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+ def test_required_usages(self):
+ """Make sure the device exports the correct required features and
+ inputs."""
+ uhdev = self.uhdev
+ rdesc = uhdev.parsed_rdesc
+ for feature in rdesc.feature_reports.values():
+ for field in feature:
+ page_id = field.usage >> 16
+ value = field.usage & 0xFF
+ try:
+ if HUT[page_id][value] == "Contact Max":
+ assert HUT[page_id][field.application] in [
+ "Touch Screen",
+ "Touch Pad",
+ "System Multi-Axis Controller",
+ ]
+ except KeyError:
+ pass
+
+ try:
+ if HUT[page_id][value] == "Inputmode":
+ assert HUT[page_id][field.application] in [
+ "Touch Screen",
+ "Touch Pad",
+ "Device Configuration",
+ ]
+ except KeyError:
+ pass
+
+ def test_mt_single_touch(self):
+ """send a single touch in the first slot of the device,
+ and release it."""
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+
+ t0 = Touch(1, 50, 100)
+ r = uhdev.event([t0])
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+
+ slot = self.get_slot(uhdev, t0, 0)
+
+ assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
+ assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
+ assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
+ assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
+
+ t0.tipswitch = False
+ if uhdev.quirks is None or "VALID_IS_INRANGE" not in uhdev.quirks:
+ t0.inrange = False
+ r = uhdev.event([t0])
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events
+ assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+ def test_mt_dual_touch(self):
+ """Send 2 touches in the first 2 slots.
+ Make sure the kernel sees this as a dual touch.
+ Release and check
+
+ Note: PTP will send here BTN_DOUBLETAP emulation"""
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+
+ t0 = Touch(1, 50, 100)
+ t1 = Touch(2, 150, 200)
+
+ if uhdev.quirks is not None and (
+ "SLOT_IS_CONTACTID" in uhdev.quirks
+ or "SLOT_IS_CONTACTNUMBER" in uhdev.quirks
+ ):
+ t1.contactid = 0
+
+ slot0 = self.get_slot(uhdev, t0, 0)
+ slot1 = self.get_slot(uhdev, t1, 1)
+
+ r = uhdev.event([t0])
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+
+ assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
+ assert evdev.value[libevdev.EV_KEY.BTN_TOUCH] == 1
+ assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
+ assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
+ assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
+ assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+ r = uhdev.event([t0, t1])
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH) not in events
+ assert evdev.value[libevdev.EV_KEY.BTN_TOUCH] == 1
+ assert (
+ libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_X, 5) not in events
+ )
+ assert (
+ libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_Y, 10) not in events
+ )
+ assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
+ assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
+ assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
+ assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1
+ assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_POSITION_X] == 150
+ assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 200
+
+ t0.tipswitch = False
+ if uhdev.quirks is None or "VALID_IS_INRANGE" not in uhdev.quirks:
+ t0.inrange = False
+ r = uhdev.event([t0, t1])
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+ assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1
+ assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_X) not in events
+ assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_Y) not in events
+
+ t1.tipswitch = False
+ if uhdev.quirks is None or "VALID_IS_INRANGE" not in uhdev.quirks:
+ t1.inrange = False
+
+ if uhdev.quirks is not None and "SLOT_IS_CONTACTNUMBER" in uhdev.quirks:
+ r = uhdev.event([t0, t1])
+ else:
+ r = uhdev.event([t1])
+
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+ assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+ @pytest.mark.skip_if_uhdev(
+ lambda uhdev: uhdev.max_contacts <= 2, "Device not compatible"
+ )
+ def test_mt_triple_tap(self):
+ """Send 3 touches in the first 3 slots.
+ Make sure the kernel sees this as a triple touch.
+ Release and check
+
+ Note: PTP will send here BTN_TRIPLETAP emulation"""
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+
+ t0 = Touch(1, 50, 100)
+ t1 = Touch(2, 150, 200)
+ t2 = Touch(3, 250, 300)
+ r = uhdev.event([t0, t1, t2])
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+
+ slot0 = self.get_slot(uhdev, t0, 0)
+ slot1 = self.get_slot(uhdev, t1, 1)
+ slot2 = self.get_slot(uhdev, t2, 2)
+
+ assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
+ assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
+ assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
+ assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1
+ assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_POSITION_X] == 150
+ assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 200
+ assert evdev.slots[slot2][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 2
+ assert evdev.slots[slot2][libevdev.EV_ABS.ABS_MT_POSITION_X] == 250
+ assert evdev.slots[slot2][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 300
+
+ t0.tipswitch = False
+ t1.tipswitch = False
+ t2.tipswitch = False
+ if uhdev.quirks is None or "VALID_IS_INRANGE" not in uhdev.quirks:
+ t0.inrange = False
+ t1.inrange = False
+ t2.inrange = False
+ r = uhdev.event([t0, t1, t2])
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+
+ assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+ assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+ assert evdev.slots[slot2][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+ @pytest.mark.skip_if_uhdev(
+ lambda uhdev: uhdev.max_contacts <= 2, "Device not compatible"
+ )
+ def test_mt_max_contact(self):
+ """send the maximum number of contact as reported by the device.
+ Make sure all contacts are forwarded and that there is no miss.
+ Release and check."""
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+
+ touches = [
+ Touch(i, (i + 3) * 20, (i + 3) * 20 + 5)
+ for i in range(uhdev.max_contacts)
+ ]
+ if (
+ uhdev.quirks is not None
+ and "SLOT_IS_CONTACTID_MINUS_ONE" in uhdev.quirks
+ ):
+ for t in touches:
+ t.contactid += 1
+ r = uhdev.event(touches)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ for i, t in enumerate(touches):
+ slot = self.get_slot(uhdev, t, i)
+
+ assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == i
+ assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_POSITION_X] == t.x
+ assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_POSITION_Y] == t.y
+
+ for t in touches:
+ t.tipswitch = False
+ if uhdev.quirks is None or "VALID_IS_INRANGE" not in uhdev.quirks:
+ t.inrange = False
+
+ r = uhdev.event(touches)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ for i, t in enumerate(touches):
+ slot = self.get_slot(uhdev, t, i)
+
+ assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+ @pytest.mark.skip_if_uhdev(
+ lambda uhdev: (
+ uhdev.touches_in_a_report == 1
+ or uhdev.quirks is not None
+ and "CONTACT_CNT_ACCURATE" not in uhdev.quirks
+ ),
+ "Device not compatible, we can not trigger the conditions",
+ )
+ def test_mt_contact_count_accurate(self):
+ """Test the MT_QUIRK_CONTACT_CNT_ACCURATE from the kernel.
+ A report should forward an accurate contact count and the kernel
+ should ignore any data provided after we have reached this
+ contact count."""
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+
+ t0 = Touch(1, 50, 100)
+ t1 = Touch(2, 150, 200)
+
+ slot0 = self.get_slot(uhdev, t0, 0)
+ slot1 = self.get_slot(uhdev, t1, 1)
+
+ r = uhdev.event([t0, t1], contact_count=1)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
+ assert evdev.value[libevdev.EV_KEY.BTN_TOUCH] == 1
+ assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_TRACKING_ID, 0) in events
+ assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
+ assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
+ assert evdev.slots[slot0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
+ assert evdev.slots[slot1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+ class TestWin8Multitouch(TestMultitouch):
+ def test_required_usages8(self):
+ """Make sure the device exports the correct required features and
+ inputs."""
+ uhdev = self.uhdev
+ rdesc = uhdev.parsed_rdesc
+ for feature in rdesc.feature_reports.values():
+ for field in feature:
+ page_id = field.usage >> 16
+ value = field.usage & 0xFF
+ try:
+ if HUT[page_id][value] == "Inputmode":
+ assert HUT[field.application] not in ["Touch Screen"]
+ except KeyError:
+ pass
+
+ @pytest.mark.skip_if_uhdev(
+ lambda uhdev: uhdev.fields.count("X") == uhdev.touches_in_a_report,
+ "Device not compatible, we can not trigger the conditions",
+ )
+ def test_mt_tx_cx(self):
+ """send a single touch in the first slot of the device, with
+ different values of Tx and Cx. Make sure the kernel reports Tx."""
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+
+ t0 = Touch(1, 5, 10)
+ t0.cx = 50
+ t0.cy = 100
+ r = uhdev.event([t0])
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 5
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TOOL_X] == 50
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 10
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TOOL_Y] == 100
+
+ @pytest.mark.skip_if_uhdev(
+ lambda uhdev: "In Range" not in uhdev.fields,
+ "Device not compatible, missing In Range usage",
+ )
+ def test_mt_inrange(self):
+ """Send one contact that has the InRange bit set before/after
+ tipswitch.
+ Kernel is supposed to mark the contact with a distance > 0
+ when inrange is set but not tipswitch.
+
+ This tests the hovering capability of devices (MT_QUIRK_HOVERING).
+
+ Make sure the contact is only released from the kernel POV
+ when the inrange bit is set to 0."""
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+
+ t0 = Touch(1, 150, 200)
+ t0.tipswitch = False
+ r = uhdev.event([t0])
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
+ assert evdev.value[libevdev.EV_KEY.BTN_TOUCH] == 1
+ assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_TRACKING_ID, 0) in events
+ assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_DISTANCE) in events
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_DISTANCE] > 0
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 150
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 200
+ assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+ t0.tipswitch = True
+ r = uhdev.event([t0])
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_DISTANCE, 0) in events
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_DISTANCE] == 0
+
+ t0.tipswitch = False
+ r = uhdev.event([t0])
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_DISTANCE) in events
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_DISTANCE] > 0
+
+ t0.inrange = False
+ r = uhdev.event([t0])
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+ def test_mt_duplicates(self):
+ """Test the MT_QUIRK_IGNORE_DUPLICATES from the kernel.
+ If a touch is reported more than once with the same Contact ID,
+ we should only handle the first touch.
+
+ Note: this is not in MS spec, but the current kernel behaves
+ like that"""
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+
+ t0 = Touch(1, 5, 10)
+ t1 = Touch(1, 15, 20)
+ t2 = Touch(2, 50, 100)
+
+ r = uhdev.event([t0, t1, t2], contact_count=2)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
+ assert evdev.value[libevdev.EV_KEY.BTN_TOUCH] == 1
+ assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_TRACKING_ID, 0) in events
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 5
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 10
+ assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1
+ assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
+ assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
+
+ def test_mt_release_miss(self):
+ """send a single touch in the first slot of the device, and
+ forget to release it. The kernel is supposed to release by itself
+ the touch in 100ms.
+ Make sure that we are dealing with a new touch by resending the
+ same touch after the timeout expired, and check that the kernel
+ considers it as a separate touch (different tracking ID)"""
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+
+ t0 = Touch(1, 5, 10)
+ r = uhdev.event([t0])
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
+
+ time.sleep(0.2)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+ r = uhdev.event([t0])
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1
+
+ @pytest.mark.skip_if_uhdev(
+ lambda uhdev: "Azimuth" not in uhdev.fields,
+ "Device not compatible, missing Azimuth usage",
+ )
+ def test_mt_azimuth(self):
+ """Check for the azimtuh information bit.
+ When azimuth is presented by the device, it should be exported
+ as ABS_MT_ORIENTATION and the exported value should report a quarter
+ of circle."""
+ uhdev = self.uhdev
+
+ t0 = Touch(1, 5, 10)
+ t0.azimuth = 270
+
+ r = uhdev.event([t0])
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+
+ # orientation is clockwise, while Azimuth is counter clockwise
+ assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_ORIENTATION, 90) in events
+
+ class TestPTP(TestWin8Multitouch):
+ def test_ptp_buttons(self):
+ """check for button reliability.
+ There are 2 types of touchpads: the click pads and the pressure pads.
+ Each should reliably report the BTN_LEFT events.
+ """
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+
+ if uhdev.type == "clickpad":
+ r = uhdev.event(click=True)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ assert libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 1) in events
+ assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 1
+
+ r = uhdev.event(click=False)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ assert libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 0) in events
+ assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0
+ else:
+ r = uhdev.event(left=True)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ assert libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 1) in events
+ assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 1
+
+ r = uhdev.event(left=False)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ assert libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 0) in events
+ assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0
+
+ r = uhdev.event(right=True)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ assert libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 1) in events
+ assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 1
+
+ r = uhdev.event(right=False)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ assert libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 0) in events
+ assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 0
+
+ @pytest.mark.skip_if_uhdev(
+ lambda uhdev: "Confidence" not in uhdev.fields,
+ "Device not compatible, missing Confidence usage",
+ )
+ def test_ptp_confidence(self):
+ """Check for the validity of the confidence bit.
+ When a contact is marked as not confident, it should be detected
+ as a palm from the kernel POV and released.
+
+ Note: if the kernel exports ABS_MT_TOOL_TYPE, it shouldn't release
+ the touch but instead convert it to ABS_MT_TOOL_PALM."""
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+
+ t0 = Touch(1, 150, 200)
+ r = uhdev.event([t0])
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+
+ t0.confidence = False
+ r = uhdev.event([t0])
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+
+ if evdev.absinfo[libevdev.EV_ABS.ABS_MT_TOOL_TYPE] is not None:
+ # the kernel exports MT_TOOL_PALM
+ assert (
+ libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_TOOL_TYPE, 2) in events
+ )
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] != -1
+
+ t0.tipswitch = False
+ r = uhdev.event([t0])
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+
+ assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+ @pytest.mark.skip_if_uhdev(
+ lambda uhdev: uhdev.touches_in_a_report >= uhdev.max_contacts,
+ "Device not compatible, we can not trigger the conditions",
+ )
+ def test_ptp_non_touch_data(self):
+ """Some single finger hybrid touchpads might not provide the
+ button information in subsequent reports (only in the first report).
+
+ Emulate this and make sure we do not release the buttons in the
+ middle of the event."""
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+
+ touches = [Touch(i, i * 10, i * 10 + 5) for i in range(uhdev.max_contacts)]
+ contact_count = uhdev.max_contacts
+ incr_scantime = True
+ btn_state = True
+ events = None
+ while touches:
+ t = touches[: uhdev.touches_in_a_report]
+ touches = touches[uhdev.touches_in_a_report :]
+ r = uhdev.event(
+ t,
+ click=btn_state,
+ left=btn_state,
+ contact_count=contact_count,
+ incr_scantime=incr_scantime,
+ )
+ contact_count = 0
+ incr_scantime = False
+ btn_state = False
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ if touches:
+ assert len(events) == 0
+
+ assert libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 1) in events
+ assert libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 0) not in events
+ assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 1
+
+
+################################################################################
+#
+# Windows 7 compatible devices
+#
+################################################################################
+class Test3m_0596_0500(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test 3m_0596_0500",
+ rdesc="05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 09 01 95 01 75 01 15 00 25 01 81 02 95 07 75 01 81 03 95 01 75 08 81 03 05 01 09 30 09 31 15 00 26 ff 7f 35 00 46 00 00 95 02 75 10 81 02 c0 a1 02 15 00 26 ff 00 09 01 95 39 75 08 81 01 c0 c0 05 0d 09 0e a1 01 85 11 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 09 04 a1 01 85 10 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 3a 06 81 02 09 31 46 e8 03 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 0a 81 02 85 12 09 55 95 01 75 08 15 00 25 0a b1 02 06 00 ff 15 00 26 ff 00 85 03 09 01 75 08 95 07 b1 02 85 04 09 01 75 08 95 17 b1 02 85 05 09 01 75 08 95 47 b1 02 85 06 09 01 75 08 95 07 b1 02 85 07 09 01 75 08 95 07 b1 02 85 08 09 01 75 08 95 07 b1 02 85 09 09 01 75 08 95 3f b1 02 c0",
+ input_info=(BusType.USB, 0x0596, 0x0500),
+ max_contacts=60,
+ quirks=("VALID_IS_CONFIDENCE", "SLOT_IS_CONTACTID", "TOUCH_SIZE_SCALING"),
+ )
+
+
+class Test3m_0596_0506(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test 3m_0596_0506",
+ rdesc="05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 09 01 95 01 75 01 15 00 25 01 81 02 95 07 75 01 81 03 95 01 75 08 81 03 05 01 09 30 09 31 15 00 26 ff 7f 35 00 46 00 00 95 02 75 10 81 02 c0 a1 02 15 00 26 ff 00 09 01 95 39 75 08 81 03 c0 c0 05 0d 09 0e a1 01 85 11 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 09 04 a1 01 85 13 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 d6 0a 81 02 09 31 46 22 06 81 02 05 0d 75 10 95 01 09 48 81 02 09 49 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 d6 0a 81 02 09 31 46 22 06 81 02 05 0d 75 10 95 01 09 48 81 02 09 49 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 d6 0a 81 02 09 31 46 22 06 81 02 05 0d 75 10 95 01 09 48 81 02 09 49 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 d6 0a 81 02 09 31 46 22 06 81 02 05 0d 75 10 95 01 09 48 81 02 09 49 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 d6 0a 81 02 09 31 46 22 06 81 02 05 0d 75 10 95 01 09 48 81 02 09 49 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 d6 0a 81 02 09 31 46 22 06 81 02 05 0d 75 10 95 01 09 48 81 02 09 49 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 3c 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 02 81 03 05 0d 85 12 09 55 95 01 75 08 15 00 25 3c b1 02 06 00 ff 15 00 26 ff 00 85 03 09 01 75 08 95 07 b1 02 85 04 09 01 75 08 95 17 b1 02 85 05 09 01 75 08 95 47 b1 02 85 06 09 01 75 08 95 07 b1 02 85 73 09 01 75 08 95 07 b1 02 85 08 09 01 75 08 95 07 b1 02 85 09 09 01 75 08 95 3f b1 02 85 0f 09 01 75 08 96 07 02 b1 02 c0",
+ input_info=(BusType.USB, 0x0596, 0x0506),
+ max_contacts=60,
+ quirks=("VALID_IS_CONFIDENCE", "SLOT_IS_CONTACTID", "TOUCH_SIZE_SCALING"),
+ )
+
+
+class TestActionStar_2101_1011(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test ActionStar_2101_1011",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 26 ff 4d 46 70 03 81 02 09 31 26 ff 2b 46 f1 01 81 02 46 00 00 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 26 ff 4d 46 70 03 81 02 09 31 26 ff 2b 46 f1 01 81 02 46 00 00 c0 05 0d 09 54 75 08 95 01 81 02 05 0d 85 02 09 55 25 02 75 08 95 01 b1 02 c0",
+ input_info=(BusType.USB, 0x2101, 0x1011),
+ )
+
+ def test_mt_actionstar_inrange(self):
+ """Special sequence that might not be handled properly"""
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+
+ # fmt: off
+ sequence = [
+ # t0 = Touch(1, 6999, 2441) | t1 = Touch(2, 15227, 2026)
+ '01 ff 01 57 1b 89 09 ff 02 7b 3b ea 07 02',
+ # t0.xy = (6996, 2450) | t1.y = 2028
+ '01 ff 01 54 1b 92 09 ff 02 7b 3b ec 07 02',
+ # t1.xy = (15233, 2040) | t0.tipswitch = False
+ '01 ff 02 81 3b f8 07 fe 01 54 1b 92 09 02',
+ # t1 | t0.inrange = False
+ '01 ff 02 81 3b f8 07 fc 01 54 1b 92 09 02',
+ ]
+ # fmt: on
+
+ for num, r_str in enumerate(sequence):
+ r = [int(i, 16) for i in r_str.split()]
+ uhdev.call_input_event(r)
+ events = uhdev.next_sync_events()
+ self.debug_reports([r], uhdev)
+ for e in events:
+ print(e)
+ if num == 2:
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+
+class TestAsus_computers_0486_0185(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test asus-computers_0486_0185",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 95 01 75 01 81 02 09 32 81 02 09 47 81 02 75 05 81 03 09 30 26 ff 00 75 08 81 02 09 51 25 02 81 02 26 96 0d 05 01 75 10 55 0d 65 33 09 30 35 00 46 fd 1d 81 02 09 31 46 60 11 81 02 c0 09 22 a1 02 05 0d 35 00 45 00 55 00 65 00 09 42 25 01 75 01 81 02 09 32 81 02 09 47 81 02 75 05 81 03 09 30 26 ff 00 75 08 81 02 09 51 25 02 81 02 26 96 0d 05 01 75 10 55 0d 65 33 09 30 46 fd 1d 81 02 09 31 46 60 11 81 02 c0 35 00 45 00 55 00 65 00 05 0d 09 54 75 08 25 02 81 02 85 08 09 55 b1 02 c0 09 0e a1 01 85 07 09 22 a1 00 09 52 25 0a b1 02 c0 05 0c 09 01 a1 01 85 06 09 01 26 ff 00 95 08 b1 02 c0 c0 05 01 09 02 a1 01 85 03 09 01 a1 00 05 09 19 01 29 02 25 01 75 01 95 02 81 02 95 06 81 03 26 96 0d 05 01 75 10 95 01 55 0d 65 33 09 30 46 fd 1d 81 02 09 31 46 60 11 81 02 c0 c0 06 ff 01 09 01 a1 01 26 ff 00 35 00 45 00 55 00 65 00 85 05 75 08 95 3f 09 00 81 02 c0",
+ input_info=(BusType.USB, 0x0486, 0x0185),
+ quirks=("VALID_IS_CONFIDENCE", "SLOT_IS_CONTACTID_MINUS_ONE"),
+ )
+
+
+class TestAtmel_03eb_201c(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test atmel_03eb_201c",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 26 ff 4b 46 70 03 81 02 09 31 26 ff 2b 46 f1 01 81 02 46 00 00 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 26 ff 4b 46 70 03 81 02 09 31 26 ff 2b 46 f1 01 81 02 46 00 00 c0 05 0d 09 54 75 08 95 01 81 02 05 0d 85 02 09 55 25 02 75 08 95 01 b1 02 c0",
+ input_info=(BusType.USB, 0x03EB, 0x201C),
+ )
+
+
+class TestAtmel_03eb_211c(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test atmel_03eb_211c",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 37 81 02 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 46 56 0a 26 ff 0f 09 30 81 02 46 b2 05 26 ff 0f 09 31 81 02 05 0d 75 08 85 02 09 55 25 10 b1 02 c0 c0",
+ input_info=(BusType.USB, 0x03EB, 0x211C),
+ )
+
+
+class TestCando_2087_0a02(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test cando_2087_0a02",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 0f 75 10 55 0e 65 33 09 30 35 00 46 6d 03 81 02 46 ec 01 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 0f 75 10 55 0e 65 33 09 30 35 00 46 6d 03 81 02 46 ec 01 09 31 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 02 81 02 85 02 09 55 b1 02 c0 06 00 ff 09 01 a1 01 85 a6 95 22 75 08 26 ff 00 15 00 09 01 81 02 85 a5 95 06 75 08 26 ff 00 15 00 09 01 91 02 c0",
+ input_info=(BusType.USB, 0x2087, 0x0A02),
+ )
+
+
+class TestCando_2087_0b03(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test cando_2087_0b03",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 26 ff 49 46 f2 03 81 02 09 31 26 ff 29 46 39 02 81 02 46 00 00 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 26 ff 49 46 f2 03 81 02 09 31 26 ff 29 46 39 02 81 02 46 00 00 c0 05 0d 09 54 75 08 95 01 81 02 05 0d 85 02 09 55 25 02 75 08 95 01 b1 02 c0",
+ input_info=(BusType.USB, 0x2087, 0x0B03),
+ )
+
+
+class TestCVTouch_1ff7_0013(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test cvtouch_1ff7_0013",
+ rdesc="06 00 ff 09 00 a1 01 85 fd 06 00 ff 09 01 09 02 09 03 09 04 09 05 09 06 15 00 26 ff 00 75 08 95 06 81 02 85 fe 06 00 ff 09 01 09 02 09 03 09 04 15 00 26 ff 00 75 08 95 04 b1 02 c0 05 01 09 02 a1 01 09 01 a1 00 85 01 05 09 19 01 29 03 15 00 25 01 95 03 75 01 81 02 95 01 75 05 81 03 05 01 09 30 09 31 15 00 26 ff 7f 35 00 46 ff 7f 75 10 95 02 81 02 05 0d 09 33 15 00 26 ff 00 35 00 46 ff 00 75 08 95 01 81 02 05 01 09 38 15 81 25 7f 35 81 45 7f 95 01 81 06 c0 c0 06 00 ff 09 00 a1 01 85 fc 15 00 26 ff 00 19 01 29 3f 75 08 95 3f 81 02 19 01 29 3f 91 02 c0 06 00 ff 09 00 a1 01 85 fb 15 00 26 ff 00 19 01 29 3f 75 08 95 3f 81 02 19 01 29 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 81 02 c0 05 0d 09 54 15 00 26 ff 00 95 01 75 08 81 02 85 03 09 55 15 00 25 02 b1 02 c0 09 0e a1 01 85 04 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0",
+ input_info=(BusType.USB, 0x1FF7, 0x0013),
+ quirks=("NOT_SEEN_MEANS_UP",),
+ )
+
+
+class TestCvtouch_1ff7_0017(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test cvtouch_1ff7_0017",
+ rdesc="06 00 ff 09 00 a1 01 85 fd 06 00 ff 09 01 09 02 09 03 09 04 09 05 09 06 15 00 26 ff 00 75 08 95 06 81 02 85 fe 06 00 ff 09 01 09 02 09 03 09 04 15 00 26 ff 00 75 08 95 04 b1 02 c0 05 01 09 02 a1 01 09 01 a1 00 85 01 05 09 19 01 29 03 15 00 25 01 95 03 75 01 81 02 95 01 75 05 81 03 05 01 09 30 09 31 15 00 26 ff 0f 35 00 46 ff 0f 75 10 95 02 81 02 09 00 15 00 25 ff 35 00 45 ff 75 08 95 01 81 02 09 38 15 81 25 7f 95 01 81 06 c0 c0 06 00 ff 09 00 a1 01 85 fc 15 00 25 ff 19 01 29 3f 75 08 95 3f 81 02 19 01 29 3f 91 02 c0 06 00 ff 09 00 a1 01 85 fb 15 00 25 ff 19 01 29 3f 75 08 95 3f 81 02 19 01 29 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 0f 75 10 55 00 65 00 09 30 35 00 46 ff 0f 81 02 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 0f 75 10 55 00 65 00 09 30 35 00 46 ff 0f 81 02 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 0f 75 10 55 00 65 00 09 30 35 00 46 ff 0f 81 02 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 0f 75 10 55 00 65 00 09 30 35 00 46 ff 0f 81 02 09 31 81 02 c0 05 0d 09 54 95 01 75 08 81 02 85 03 09 55 25 02 b1 02 c0 09 0e a1 01 85 04 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0",
+ input_info=(BusType.USB, 0x1FF7, 0x0017),
+ )
+
+
+class TestCypress_04b4_c001(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test cypress_04b4_c001",
+ rdesc="05 01 09 02 a1 01 85 01 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 95 03 75 01 81 02 95 01 75 05 81 01 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0 05 0d 09 04 a1 01 85 02 09 22 09 53 95 01 75 08 81 02 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 15 00 25 20 09 48 81 02 09 49 81 02 05 01 15 00 26 d0 07 75 10 55 00 65 00 09 30 15 00 26 d0 07 35 00 45 00 81 02 09 31 45 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 15 00 25 20 09 48 81 02 09 49 81 02 05 01 15 00 26 d0 07 75 10 55 00 65 00 09 30 15 00 26 d0 07 35 00 45 00 81 02 09 31 45 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 15 00 25 20 09 48 81 02 09 49 81 02 05 01 15 00 26 d0 07 75 10 55 00 65 00 09 30 15 00 26 d0 07 35 00 45 00 81 02 09 31 45 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 15 00 25 20 09 48 81 02 09 49 81 02 05 01 15 00 26 d0 07 75 10 55 00 65 00 09 30 15 00 26 d0 07 35 00 45 00 81 02 09 31 45 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 15 00 25 20 09 48 81 02 09 49 81 02 05 01 15 00 26 d0 07 75 10 55 00 65 00 09 30 15 00 26 d0 07 35 00 45 00 81 02 09 31 45 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 15 00 25 20 09 48 81 02 09 49 81 02 05 01 15 00 26 d0 07 75 10 55 00 65 00 09 30 15 00 26 d0 07 35 00 45 00 81 02 09 31 45 00 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 0a 81 02 09 55 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0",
+ input_info=(BusType.USB, 0x04B4, 0xC001),
+ )
+
+
+class TestData_modul_7374_1232(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test data-modul_7374_1232",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 37 81 02 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 46 d0 07 26 ff 0f 09 30 81 02 46 40 06 09 31 81 02 05 0d 75 08 85 02 09 55 25 10 b1 02 c0 c0",
+ input_info=(BusType.USB, 0x7374, 0x1232),
+ )
+
+
+class TestData_modul_7374_1252(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test data-modul_7374_1252",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 37 81 02 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 46 d0 07 26 ff 0f 09 30 81 02 46 40 06 09 31 81 02 05 0d 75 08 85 02 09 55 25 10 b1 02 c0 c0",
+ input_info=(BusType.USB, 0x7374, 0x1252),
+ )
+
+
+class TestE4_2219_044c(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test e4_2219_044c",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 08 81 02 09 55 b1 02 c0 09 0e a1 01 85 02 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 85 03 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 95 03 75 01 81 02 95 01 75 05 81 01 05 01 09 30 09 31 15 00 26 ff 7f 75 10 95 02 81 02 05 01 09 38 15 81 25 7f 75 08 95 01 81 06 c0 c0",
+ input_info=(BusType.USB, 0x2219, 0x044C),
+ )
+
+
+class TestEgalax_capacitive_0eef_7224(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test egalax-capacitive_0eef_7224",
+ rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 34 49 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 37 29 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 34 49 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 37 29 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0",
+ input_info=(BusType.USB, 0x0EEF, 0x7224),
+ quirks=("SLOT_IS_CONTACTID", "ALWAYS_VALID"),
+ )
+
+
+class TestEgalax_capacitive_0eef_72fa(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test egalax-capacitive_0eef_72fa",
+ rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 72 22 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 87 13 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 72 22 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 87 13 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0",
+ input_info=(BusType.USB, 0x0EEF, 0x72FA),
+ quirks=("SLOT_IS_CONTACTID", "VALID_IS_INRANGE"),
+ )
+
+
+class TestEgalax_capacitive_0eef_7336(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test egalax-capacitive_0eef_7336",
+ rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 c1 20 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 c2 18 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 c1 20 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 c2 18 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0",
+ input_info=(BusType.USB, 0x0EEF, 0x7336),
+ )
+
+
+class TestEgalax_capacitive_0eef_7337(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test egalax-capacitive_0eef_7337",
+ rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 ae 17 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 c3 0e 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 ae 17 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 c3 0e 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0",
+ input_info=(BusType.USB, 0x0EEF, 0x7337),
+ )
+
+
+class TestEgalax_capacitive_0eef_7349(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test egalax-capacitive_0eef_7349",
+ rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 34 49 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 37 29 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 34 49 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 37 29 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0",
+ input_info=(BusType.USB, 0x0EEF, 0x7349),
+ quirks=("SLOT_IS_CONTACTID", "ALWAYS_VALID"),
+ )
+
+
+class TestEgalax_capacitive_0eef_73f4(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test egalax-capacitive_0eef_73f4",
+ rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 96 4e 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 23 2c 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 96 4e 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 23 2c 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0",
+ input_info=(BusType.USB, 0x0EEF, 0x73F4),
+ )
+
+
+class TestEgalax_capacitive_0eef_a001(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test egalax-capacitive_0eef_a001",
+ rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 23 28 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 11 19 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0",
+ input_info=(BusType.USB, 0x0EEF, 0xA001),
+ quirks=("SLOT_IS_CONTACTID", "VALID_IS_INRANGE"),
+ )
+
+
+class TestElo_touchsystems_04e7_0022(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test elo-touchsystems_04e7_0022",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 0f 75 10 55 0e 65 33 09 30 35 00 46 ff 0f 81 02 46 ff 0f 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 0f 75 10 55 00 65 00 09 30 35 00 46 ff 0f 81 02 46 ff 0f 09 31 81 02 c0 05 0d 09 54 25 10 95 01 75 08 81 02 85 08 09 55 25 02 b1 02 c0 09 0e a1 01 85 07 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 06 00 ff 09 55 85 80 15 00 26 ff 00 75 08 95 01 b1 82 c0 05 01 09 02 a1 01 85 54 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 15 00 26 ff 0f 75 10 95 01 81 02 09 31 75 10 95 01 81 02 09 3b 16 00 00 26 00 01 36 00 00 46 00 01 66 00 00 75 10 95 01 81 62 c0 c0",
+ input_info=(BusType.USB, 0x04E7, 0x0022),
+ )
+
+
+class TestElo_touchsystems_04e7_0080(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test elo-touchsystems_04e7_0080",
+ rdesc="05 0d 09 04 a1 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 09 47 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 46 7c 24 75 10 95 01 09 30 81 02 09 31 46 96 14 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 09 47 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 46 7c 24 75 10 95 01 09 30 81 02 09 31 46 96 14 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 09 47 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 46 7c 24 75 10 95 01 09 30 81 02 09 31 46 96 14 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 09 47 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 46 7c 24 75 10 95 01 09 30 81 02 09 31 46 96 14 81 02 c0 05 0d 09 54 75 08 95 01 15 00 25 08 81 02 09 55 b1 02 c0",
+ input_info=(BusType.USB, 0x04E7, 0x0080),
+ )
+
+
+class TestFlatfrog_25b5_0002(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test flatfrog_25b5_0002",
+ rdesc="05 0d 09 04 a1 01 85 05 09 22 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 05 0d 09 22 65 00 55 00 a1 02 05 0d 15 00 25 01 75 01 95 01 09 42 81 02 09 32 81 02 95 06 81 03 75 08 95 01 25 7f 09 51 81 02 05 01 65 11 55 0e 75 10 35 00 26 a6 2b 46 48 1b 09 30 81 02 26 90 18 46 59 0f 09 31 81 02 05 0d 65 11 55 0f 75 08 25 7f 45 7f 09 48 81 02 09 49 81 02 65 00 55 00 75 10 26 00 04 46 00 04 09 30 81 02 c0 65 00 55 00 05 0d 55 0c 66 01 10 75 20 95 01 27 ff ff ff 7f 45 00 09 56 81 02 75 08 95 01 15 00 25 28 09 54 81 02 09 55 85 06 25 28 b1 02 c0 65 00 55 00 45 00 09 0e a1 01 85 03 09 23 a1 02 09 52 15 02 25 02 75 08 95 01 b1 02 09 53 15 00 25 0a 75 08 95 01 b1 02 c0 c0",
+ input_info=(BusType.USB, 0x25B5, 0x0002),
+ quirks=("NOT_SEEN_MEANS_UP", "NO_AREA"),
+ max_contacts=40,
+ )
+
+
+class TestFocaltech_10c4_81b9(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test focaltech_10c4_81b9",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 04 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 26 58 02 09 31 46 00 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 04 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 26 58 02 09 31 46 00 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 04 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 26 58 02 09 31 46 00 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 04 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 26 58 02 09 31 46 00 00 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 04 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 26 58 02 09 31 46 00 00 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 08 81 02 85 02 09 55 75 08 95 01 b1 02 c0",
+ input_info=(BusType.USB, 0x10C4, 0x81B9),
+ quirks=("ALWAYS_VALID",),
+ max_contacts=5,
+ )
+
+
+class TestHanvon_20b3_0a18(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test hanvon_20b3_0a18",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 26 ff 4b 46 70 03 81 02 09 31 26 ff 2b 46 f1 01 81 02 46 00 00 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 26 ff 4b 46 70 03 81 02 09 31 26 ff 2b 46 f1 01 81 02 46 00 00 c0 05 0d 09 54 75 08 95 01 81 02 05 0d 85 02 09 55 25 02 75 08 95 01 b1 02 c0",
+ input_info=(BusType.USB, 0x20B3, 0x0A18),
+ )
+
+
+class TestHuitoo_03f7_0003(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test huitoo_03f7_0003",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 00 65 00 35 00 46 ff 0f 09 30 26 ff 0f 81 02 09 31 26 ff 0f 81 02 05 0d 09 48 26 ff 0f 81 02 09 49 26 ff 0f 81 02 c0 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 00 65 00 35 00 46 ff 0f 09 30 26 ff 0f 81 02 09 31 26 ff 0f 81 02 05 0d 09 48 26 ff 0f 81 02 09 49 26 ff 0f 81 02 c0 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 00 65 00 35 00 46 ff 0f 09 30 26 ff 0f 81 02 09 31 26 ff 0f 81 02 05 0d 09 48 26 ff 0f 81 02 09 49 26 ff 0f 81 02 c0 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 00 65 00 35 00 46 ff 0f 09 30 26 ff 0f 81 02 09 31 26 ff 0f 81 02 05 0d 09 48 26 ff 0f 81 02 09 49 26 ff 0f 81 02 c0 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 00 65 00 35 00 46 ff 0f 09 30 26 ff 0f 81 02 09 31 26 ff 0f 81 02 05 0d 09 48 26 ff 0f 81 02 09 49 26 ff 0f 81 02 c0 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 00 65 00 35 00 46 ff 0f 09 30 26 ff 0f 81 02 09 31 26 ff 0f 81 02 05 0d 09 48 26 ff 0f 81 02 09 49 26 ff 0f 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 08 81 02 09 55 b1 02 c0 09 0e a1 01 85 02 09 23 a1 02 09 52 09 53 15 00 25 10 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 85 03 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 95 03 75 01 81 02 95 01 75 05 81 01 05 01 09 30 09 31 15 00 26 ff 0f 35 00 46 ff 0f 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 85 04 15 00 26 ff 00 75 08 95 3f 09 02 81 02 95 3f 09 02 91 02 c0",
+ input_info=(BusType.USB, 0x03F7, 0x0003),
+ )
+
+
+class TestIdeacom_1cb6_6650(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test ideacom_1cb6_6650",
+ rdesc="05 0d 09 04 a1 01 85 0a 09 22 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 81 03 05 01 26 ff 1f 75 10 95 01 55 0d 65 33 09 31 35 00 46 61 13 81 02 09 30 46 73 22 81 02 05 0d 75 08 95 01 09 30 26 ff 00 81 02 09 51 81 02 85 0c 09 55 25 02 95 01 b1 02 c0 06 00 ff 85 02 09 01 75 08 95 07 b1 02 85 03 09 02 75 08 95 07 b1 02 85 04 09 03 75 08 95 07 b1 02 85 05 09 04 75 08 95 07 b1 02 85 06 09 05 75 08 96 27 00 b1 02 85 07 09 06 75 08 96 27 00 b1 02 85 08 09 07 75 08 95 07 b1 02 85 09 09 08 75 08 95 07 b1 02 85 0b 09 09 75 08 96 07 00 b1 02 85 0d 09 0a 75 08 96 27 00 b1 02 c0 09 0e a1 01 85 0e 09 52 09 53 95 07 b1 02 c0 05 01 09 02 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 75 06 95 01 81 01 05 01 09 31 09 30 15 00 27 ff 1f 00 00 75 10 95 02 81 02 c0 09 01 a1 02 15 00 26 ff 00 95 02 75 08 81 03 c0 c0",
+ input_info=(BusType.USB, 0x1CB6, 0x6650),
+ )
+
+
+class TestIdeacom_1cb6_6651(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test ideacom_1cb6_6651",
+ rdesc="05 0d 09 04 a1 01 85 0a 09 22 a1 02 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 81 03 05 01 26 ff 1f 75 10 95 01 55 0d 65 33 09 31 35 00 46 39 13 81 02 09 30 46 24 22 81 02 05 0d 75 08 95 01 09 30 26 ff 00 81 02 09 51 81 02 85 0c 09 55 25 02 95 01 b1 02 c0 06 00 ff 85 02 09 01 75 08 95 07 b1 02 85 03 09 02 75 08 95 07 b1 02 85 04 09 03 75 08 95 07 b1 02 85 05 09 04 75 08 95 07 b1 02 85 06 09 05 75 08 95 1f b1 02 85 07 09 06 75 08 96 1f 00 b1 02 85 08 09 07 75 08 95 07 b1 02 85 09 09 08 75 08 95 07 b1 02 85 0b 09 09 75 08 95 07 b1 02 85 0d 09 0a 75 08 96 1f 00 b1 02 c0 09 0e a1 01 85 0e 09 52 09 53 95 07 b1 02 c0 05 01 09 02 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 75 06 95 01 81 01 05 01 09 31 09 30 15 00 27 ff 1f 00 00 75 10 95 02 81 02 c0 09 01 a1 02 15 00 26 ff 00 95 02 75 08 81 03 c0 c0",
+ input_info=(BusType.USB, 0x1CB6, 0x6651),
+ )
+
+
+class TestIkaist_2793_0001(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test ikaist_2793_0001",
+ rdesc="05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 09 01 95 01 75 01 15 00 25 01 81 02 95 07 75 01 81 03 95 01 75 08 81 03 05 01 09 30 09 31 15 00 26 ff 7f 35 00 46 00 00 95 02 75 10 81 02 c0 a1 02 15 00 26 ff 00 09 01 95 39 75 08 81 03 c0 c0 05 0d 09 0e a1 01 85 11 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 09 04 a1 01 85 13 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 33 09 30 35 00 46 51 07 81 02 09 31 46 96 04 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 3c 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 02 81 03 05 0d 85 12 09 55 95 01 75 08 15 00 25 3c b1 02 06 00 ff 15 00 26 ff 00 85 1e 09 01 75 08 95 80 b1 02 85 1f 09 01 75 08 96 3f 01 b1 02 c0",
+ input_info=(BusType.USB, 0x2793, 0x0001),
+ )
+
+
+class TestIrmtouch_23c9_5666(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test irmtouch_23c9_5666",
+ rdesc="05 0d 09 04 a1 01 85 0a 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 09 30 81 02 09 31 81 02 05 0d 09 48 09 49 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 09 30 81 02 09 31 81 02 05 0d 09 48 09 49 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 09 30 81 02 09 31 81 02 05 0d 09 48 09 49 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 09 30 81 02 09 31 81 02 05 0d 09 48 09 49 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 09 30 81 02 09 31 81 02 05 0d 09 48 09 49 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 15 00 26 ff 7f 75 10 09 30 81 02 09 31 81 02 05 0d 09 48 09 49 95 02 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 55 25 06 b1 02 c0 09 0e a1 01 85 0c 09 23 a1 02 09 52 15 00 25 06 75 08 95 01 b1 02 c0 c0",
+ input_info=(BusType.USB, 0x23C9, 0x5666),
+ )
+
+
+class TestIrtouch_6615_0070(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test irtouch_6615_0070",
+ rdesc="05 01 09 02 a1 01 85 10 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 06 81 03 05 01 09 30 09 31 15 00 26 ff 7f 75 10 95 02 81 02 c0 c0 05 0d 09 04 a1 01 85 30 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 09 30 26 ff 7f 55 0f 65 11 35 00 46 51 02 75 10 95 01 81 02 09 31 35 00 46 73 01 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 09 30 26 ff 7f 55 0f 65 11 35 00 46 51 02 75 10 95 01 81 02 09 31 35 00 46 73 01 81 02 c0 05 0d 09 54 15 00 26 02 00 75 08 95 01 81 02 85 03 09 55 15 00 26 ff 00 75 08 95 01 b1 02 c0 05 0d 09 0e a1 01 85 02 09 52 09 53 15 00 26 ff 00 75 08 95 02 b1 02 c0 05 0d 09 02 a1 01 85 20 09 20 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 05 01 09 30 26 ff 7f 55 0f 65 11 35 00 46 51 02 75 10 95 01 81 02 09 31 35 00 46 73 01 81 02 85 01 06 00 ff 09 01 75 08 95 01 b1 02 c0 c0",
+ input_info=(BusType.USB, 0x6615, 0x0070),
+ )
+
+
+class TestIrtouch_6615_0081(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test irtouch_6615_0081",
+ rdesc="05 0d 09 04 a1 01 85 30 09 22 09 00 15 00 26 ff 00 75 08 95 05 81 02 a1 00 05 0d 09 51 15 00 26 ff 00 75 08 95 01 81 02 05 01 09 30 26 ff 7f 55 0e 65 13 35 00 46 b5 04 75 10 95 01 81 02 09 31 35 00 46 8a 03 81 02 09 32 35 00 46 8a 03 81 02 09 00 15 00 26 ff 7f 75 10 95 01 81 02 09 00 15 00 26 ff 7f 75 10 95 01 81 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 00 15 00 26 ff 00 75 08 95 01 81 02 c0 a1 00 05 0d 09 51 15 00 26 ff 00 75 08 95 01 81 02 05 01 09 30 26 ff 7f 55 0e 65 13 35 00 46 b5 04 75 10 95 01 81 02 09 31 35 00 46 8a 03 81 02 09 32 35 00 46 8a 03 81 02 09 00 15 00 26 ff 7f 75 10 95 01 81 02 09 00 15 00 26 ff 7f 75 10 95 01 81 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 00 15 00 26 ff 00 75 08 95 01 81 02 c0 a1 00 05 0d 09 51 15 00 26 ff 00 75 08 95 01 81 02 05 01 09 30 26 ff 7f 55 0e 65 13 35 00 46 b5 04 75 10 95 01 81 02 09 31 35 00 46 8a 03 81 02 09 32 35 00 46 8a 03 81 02 09 00 15 00 26 ff 7f 75 10 95 01 81 02 09 00 15 00 26 ff 7f 75 10 95 01 81 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 00 15 00 26 ff 00 75 08 95 01 81 02 c0 a1 00 05 0d 09 54 15 00 25 1f 75 05 95 01 81 02 09 00 15 00 25 07 75 03 95 01 81 02 09 00 15 00 26 ff 00 75 08 95 01 81 02 c0 09 55 85 03 15 00 26 ff 00 75 08 95 01 b1 02 c0 05 0d 09 0e a1 01 85 02 09 52 09 53 15 00 26 ff 00 75 08 95 02 b1 02 c0 06 00 ff 09 00 a1 01 09 02 a1 00 85 aa 09 06 15 00 26 ff 00 35 00 46 ff 00 75 08 95 3f b1 02 c0 c0 05 01 09 02 a1 01 85 10 09 01 a1 00 05 01 09 00 15 00 26 ff 00 75 08 95 05 81 02 09 30 09 31 09 32 15 00 26 ff 7f 75 10 95 03 81 02 05 09 19 01 29 08 15 00 25 01 95 08 75 01 81 02 09 00 15 00 26 ff 00 75 08 95 02 81 02 c0 c0 06 00 ff 09 00 a1 01 85 40 09 00 15 00 26 ff 00 75 08 95 2e 81 02 c0",
+ input_info=(BusType.USB, 0x6615, 0x0081),
+ )
+
+
+class TestLG_043e_9aa1(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test lg_043e_9aa1",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 09 31 46 78 0a 26 38 04 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 0a 81 02 25 0a 09 55 b1 02 c0 09 0e a1 01 85 03 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 85 04 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 75 10 95 01 15 00 26 7f 07 81 02 09 31 26 37 04 81 02 c0 c0 06 00 ff 09 01 a1 01 85 05 15 00 26 ff 00 75 08 95 19 09 01 b1 02 c0 05 14 09 2b a1 02 85 07 09 2b 15 00 25 0a 75 08 95 40 b1 02 09 4b 15 00 25 0a 75 08 95 02 91 02 c0 05 14 09 2c a1 02 85 08 09 2b 15 00 25 0a 75 08 95 05 81 02 09 4b 15 00 25 0a 75 08 95 47 91 02 c0",
+ input_info=(BusType.USB, 0x043E, 0x9AA1),
+ )
+
+
+class TestLG_043e_9aa3(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test lg_043e_9aa3",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 09 31 46 78 0a 26 38 04 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 0a 81 02 25 0a 09 55 b1 02 c0 09 0e a1 01 85 03 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 85 04 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 75 10 95 01 15 00 26 7f 07 81 02 09 31 26 37 04 81 02 c0 c0 06 00 ff 09 01 a1 01 85 05 15 00 26 ff 00 75 08 95 19 09 01 b1 02 c0 05 14 09 2b a1 02 85 07 09 2b 15 00 25 0a 75 08 95 40 b1 02 09 4b 15 00 25 0a 75 08 95 02 91 02 c0 05 14 09 2c a1 02 85 08 09 2b 15 00 25 0a 75 08 95 05 81 02 09 4b 15 00 25 0a 75 08 95 47 91 02 c0",
+ input_info=(BusType.USB, 0x043E, 0x9AA3),
+ )
+
+
+class TestLG_1fd2_0064(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test lg_1fd2_0064",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 a1 00 05 01 26 80 07 75 10 55 0e 65 33 09 30 35 00 46 53 07 81 02 26 38 04 46 20 04 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 a1 00 05 01 26 80 07 75 10 55 0e 65 33 09 30 35 00 46 53 07 81 02 26 38 04 46 20 04 09 31 81 02 45 00 c0 c0 05 0d 09 54 95 01 75 08 81 02 85 08 09 55 95 01 25 02 b1 02 c0 09 0e a1 01 85 07 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 85 03 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 75 10 95 02 15 00 26 ff 7f 81 02 c0 c0",
+ input_info=(BusType.USB, 0x1FD2, 0x0064),
+ )
+
+
+class TestLumio_202e_0006(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test lumio_202e_0006",
+ rdesc="05 0d 09 04 a1 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 09 47 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 46 b0 0e 75 10 95 01 09 30 81 02 09 31 46 c2 0b 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 09 47 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 46 b0 0e 75 10 95 01 09 30 81 02 09 31 46 c2 0b 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 09 47 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 46 b0 0e 75 10 95 01 09 30 81 02 09 31 46 c2 0b 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 09 47 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 46 b0 0e 75 10 95 01 09 30 81 02 09 31 46 c2 0b 81 02 c0 05 0d 09 54 75 08 95 01 15 00 25 08 81 02 09 55 b1 02 c0",
+ input_info=(BusType.USB, 0x202E, 0x0006),
+ quirks=("VALID_IS_CONFIDENCE", "SLOT_IS_CONTACTID_MINUS_ONE"),
+ )
+
+
+class TestLumio_202e_0007(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test lumio_202e_0007",
+ rdesc="05 0d 09 04 a1 01 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 09 47 95 02 81 02 95 0a 81 03 05 01 26 ff 7f 65 11 55 0e 46 ba 0e 75 10 95 01 09 30 81 02 09 31 46 ea 0b 81 02 05 0d 09 51 75 10 95 01 81 02 09 55 15 00 25 08 75 08 95 01 b1 02 c0 c0",
+ input_info=(BusType.USB, 0x202E, 0x0007),
+ quirks=("VALID_IS_CONFIDENCE", "SLOT_IS_CONTACTID_MINUS_ONE"),
+ )
+
+
+class TestNexio_1870_0100(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test nexio_1870_0100",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 05 0d 09 54 95 01 75 08 25 02 81 02 85 02 09 55 25 02 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 95 03 75 01 19 01 29 03 15 00 25 01 81 02 95 01 75 05 81 01 05 01 75 10 95 02 09 30 09 31 15 00 26 ff 7f 81 02 c0 c0 05 0d 09 02 a1 01 85 05 09 20 a1 00 09 42 09 32 15 00 25 01 75 01 95 02 81 02 95 0e 81 03 05 01 26 ff 3f 75 10 95 01 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 c0 06 00 ff 09 01 a1 01 85 06 19 01 29 40 15 00 26 ff 00 75 08 95 40 81 00 19 01 29 40 91 00 c0",
+ input_info=(BusType.USB, 0x1870, 0x0100),
+ )
+
+
+class TestNexio_1870_010d(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test nexio_1870_010d",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 05 0d 09 54 95 01 75 08 25 02 81 02 85 02 09 55 25 06 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 95 03 75 01 19 01 29 03 15 00 25 01 81 02 95 01 75 05 81 01 05 01 75 10 95 02 09 30 09 31 15 00 26 ff 7f 81 02 c0 c0 05 0d 09 02 a1 01 85 05 09 20 a1 00 09 42 09 32 15 00 25 01 75 01 95 02 81 02 95 0e 81 03 05 01 26 ff 3f 75 10 95 01 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 c0 06 00 ff 09 01 a1 01 85 06 19 01 29 40 15 00 26 ff 00 75 08 95 3e 81 00 19 01 29 40 91 00 c0",
+ input_info=(BusType.USB, 0x1870, 0x010D),
+ )
+
+
+class TestNexio_1870_0119(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test nexio_1870_0119",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 05 0d 09 54 95 01 75 08 25 02 81 02 85 02 09 55 25 06 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 95 03 75 01 19 01 29 03 15 00 25 01 81 02 95 01 75 05 81 01 05 01 75 10 95 02 09 30 09 31 15 00 26 ff 7f 81 02 c0 c0 05 0d 09 02 a1 01 85 05 09 20 a1 00 09 42 09 32 15 00 25 01 75 01 95 02 81 02 95 0e 81 03 05 01 26 ff 3f 75 10 95 01 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 c0 06 00 ff 09 01 a1 01 85 06 19 01 29 40 15 00 26 ff 00 75 08 95 3e 81 00 19 01 29 40 91 00 c0",
+ input_info=(BusType.USB, 0x1870, 0x0119),
+ )
+
+
+class TestPenmount_14e1_3500(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test penmount_14e1_3500",
+ rdesc="05 0d 09 04 a1 01 09 22 a1 00 09 51 15 00 25 0f 75 04 95 01 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 81 01 05 01 75 10 95 01 09 30 26 ff 07 81 02 09 31 26 ff 07 81 02 05 0d 09 55 75 08 95 05 b1 02 c0 c0",
+ input_info=(BusType.USB, 0x14E1, 0x3500),
+ quirks=("VALID_IS_CONFIDENCE",),
+ max_contacts=10,
+ )
+
+
+class TestPixart_093a_8002(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test pixart_093a_8002",
+ rdesc="05 01 09 02 a1 01 85 0d 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 03 05 01 55 0e 65 11 75 10 95 01 35 00 46 5a 14 26 ff 7f 09 30 81 22 46 72 0b 26 ff 7f 09 31 81 22 95 08 75 08 81 03 c0 c0 05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 5a 14 26 ff 7f 81 02 09 31 46 72 0b 26 ff 7f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 5a 14 26 ff 7f 81 02 46 72 0b 26 ff 7f 09 31 81 02 c0 05 0d 09 54 15 00 26 ff 00 95 01 75 08 81 02 09 55 25 02 95 01 85 02 b1 02 c0 05 0d 09 0e a1 01 06 00 ff 09 01 26 ff 00 75 08 95 47 85 03 b1 02 09 01 96 ff 03 85 04 b1 02 09 01 95 0b 85 05 b1 02 09 01 96 ff 03 85 06 b1 02 09 01 95 0f 85 07 b1 02 09 01 96 ff 03 85 08 b1 02 09 01 96 ff 03 85 09 b1 02 09 01 95 3f 85 0a b1 02 09 01 96 ff 03 85 0b b1 02 09 01 96 c3 03 85 0e b1 02 09 01 96 ff 03 85 0f b1 02 09 01 96 83 03 85 10 b1 02 09 01 96 93 00 85 11 b1 02 09 01 96 ff 03 85 12 b1 02 05 0d 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 85 0c b1 02 c0 c0",
+ input_info=(BusType.USB, 0x093A, 0x8002),
+ quirks=("VALID_IS_INRANGE", "SLOT_IS_CONTACTNUMBER"),
+ )
+
+
+class TestPqlabs_1ef1_0001(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test pqlabs_1ef1_0001",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 05 0d 09 54 95 01 75 08 25 02 81 02 85 02 09 55 25 02 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 95 03 75 01 19 01 29 03 15 00 25 01 81 02 95 01 75 05 81 01 05 01 75 10 95 02 09 30 09 31 15 00 26 ff 3f 81 02 c0 c0 05 8c 09 07 a1 01 85 11 09 02 15 00 26 ff 00 75 08 95 3f 81 02 85 10 09 10 91 02 c0",
+ input_info=(BusType.USB, 0x1EF1, 0x0001),
+ )
+
+
+class TestQuanta_0408_3000(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test quanta_0408_3000",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 e3 13 26 7f 07 81 02 09 31 46 2f 0b 26 37 04 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 e3 13 26 7f 07 81 02 46 2f 0b 26 37 04 09 31 81 02 c0 05 0d 09 54 15 00 26 ff 00 95 01 75 08 81 02 09 55 25 02 95 01 85 02 b1 02 06 00 ff 09 01 26 ff 00 75 08 95 2f 85 03 b1 02 09 01 96 ff 03 85 04 b1 02 09 01 95 0b 85 05 b1 02 09 01 96 ff 03 85 06 b1 02 c0",
+ input_info=(BusType.USB, 0x0408, 0x3000),
+ )
+
+
+class TestQuanta_0408_3001(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test quanta_0408_3001",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 09 31 46 78 0a 26 38 04 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 80 07 81 02 46 78 0a 26 38 04 09 31 81 02 c0 05 0d 09 54 15 00 26 ff 00 95 01 75 08 81 02 09 55 25 02 95 01 85 02 b1 02 06 00 ff 09 01 26 ff 00 75 08 95 47 85 03 b1 02 09 01 96 ff 03 85 04 b1 02 09 01 95 0b 85 05 b1 02 09 01 96 ff 03 85 06 b1 02 09 01 95 0f 85 07 b1 02 09 01 96 ff 03 85 08 b1 02 09 01 96 ff 03 85 09 b1 02 09 01 95 0f 85 0a b1 02 09 01 96 ff 03 85 0b b1 02 c0",
+ input_info=(BusType.USB, 0x0408, 0x3001),
+ quirks=("VALID_IS_CONFIDENCE", "SLOT_IS_CONTACTID"),
+ )
+
+
+class TestQuanta_0408_3008_1(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test quanta_0408_3008_1",
+ rdesc="05 01 09 02 a1 01 85 0d 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 03 05 01 55 0e 65 11 75 10 95 01 35 00 46 4c 11 26 7f 07 09 30 81 22 46 bb 09 26 37 04 09 31 81 22 95 08 75 08 81 03 c0 c0 05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 4c 11 26 7f 07 81 02 09 31 46 bb 09 26 37 04 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 4c 11 26 7f 07 81 02 46 bb 09 26 37 04 09 31 81 02 c0 05 0d 09 54 15 00 26 ff 00 95 01 75 08 81 02 09 55 25 02 95 01 85 02 b1 02 c0 05 0d 09 0e a1 01 06 00 ff 09 01 26 ff 00 75 08 95 47 85 03 b1 02 09 01 96 ff 03 85 04 b1 02 09 01 95 0b 85 05 b1 02 09 01 96 ff 03 85 06 b1 02 09 01 95 0f 85 07 b1 02 09 01 96 ff 03 85 08 b1 02 09 01 96 ff 03 85 09 b1 02 09 01 95 3f 85 0a b1 02 09 01 96 ff 03 85 0b b1 02 09 01 96 c3 03 85 0e b1 02 09 01 96 ff 03 85 0f b1 02 09 01 96 83 03 85 10 b1 02 09 01 96 93 00 85 11 b1 02 09 01 96 ff 03 85 12 b1 02 05 0d 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 85 0c b1 02 c0 c0",
+ input_info=(BusType.USB, 0x0408, 0x3008),
+ )
+
+
+class TestQuanta_0408_3008(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test quanta_0408_3008",
+ rdesc="05 01 09 02 a1 01 85 0d 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 03 05 01 55 0e 65 11 75 10 95 01 35 00 46 98 12 26 7f 07 09 30 81 22 46 78 0a 26 37 04 09 31 81 22 c0 c0 05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 7f 07 81 02 09 31 46 78 0a 26 37 04 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 7f 07 81 02 46 78 0a 26 37 04 09 31 81 02 c0 05 0d 09 54 15 00 26 ff 00 95 01 75 08 81 02 09 55 25 02 95 01 85 02 b1 02 c0 05 0d 09 0e a1 01 06 00 ff 09 01 26 ff 00 75 08 95 47 85 03 b1 02 09 01 96 ff 03 85 04 b1 02 09 01 95 0b 85 05 b1 02 09 01 96 ff 03 85 06 b1 02 09 01 95 0f 85 07 b1 02 09 01 96 ff 03 85 08 b1 02 09 01 96 ff 03 85 09 b1 02 09 01 95 3f 85 0a b1 02 09 01 96 ff 03 85 0b b1 02 09 01 96 c3 03 85 0e b1 02 09 01 96 ff 03 85 0f b1 02 09 01 96 83 03 85 10 b1 02 09 01 96 93 00 85 11 b1 02 09 01 96 ff 03 85 12 b1 02 05 0d 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 85 0c b1 02 c0 c0",
+ input_info=(BusType.USB, 0x0408, 0x3008),
+ )
+
+
+class TestRafi_05bd_0107(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test rafi_05bd_0107",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 65 00 55 00 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 25 09 95 01 81 02 05 01 46 9c 01 26 ff 03 35 00 75 10 09 30 81 02 46 e7 00 26 ff 03 09 31 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 09 81 02 05 0d 85 02 95 01 75 08 09 55 25 0a b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 05 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 06 81 03 05 01 65 11 55 0f 09 30 26 ff 03 35 00 46 9c 01 75 10 95 01 81 02 09 31 26 ff 03 35 00 46 e7 00 81 02 c0 c0",
+ input_info=(BusType.USB, 0x05BD, 0x0107),
+ )
+
+
+class TestRndplus_2512_5003(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test rndplus_2512_5003",
+ rdesc="05 0d 09 04 a1 01 85 02 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 08 81 02 85 08 09 55 b1 02 c0 09 0e a1 01 85 07 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 85 03 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 95 03 75 01 81 02 95 01 75 05 81 01 05 01 09 30 09 31 16 00 00 26 ff 3f 36 00 00 46 ff 3f 66 00 00 75 10 95 02 81 62 c0 c0",
+ input_info=(BusType.USB, 0x2512, 0x5003),
+ )
+
+
+class TestRndplus_2512_5004(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test rndplus_2512_5004",
+ rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 00 65 00 09 30 35 00 46 00 00 81 02 09 31 46 00 00 81 02 05 0d 09 48 09 49 75 10 95 02 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 08 81 02 85 05 09 55 b1 02 c0 09 0e a1 01 85 06 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 85 03 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 95 03 75 01 81 02 95 01 75 05 81 01 05 01 09 30 09 31 16 00 00 26 ff 3f 36 00 00 46 ff 3f 66 00 00 75 10 95 02 81 62 c0 c0 06 00 ff 09 01 a1 01 85 01 09 01 15 00 26 ff 00 75 08 95 3f 82 00 01 85 02 09 01 15 00 26 ff 00 75 08 95 3f 92 00 01 c0",
+ input_info=(BusType.USB, 0x2512, 0x5004),
+ )
+
+
+class TestSitronix_1403_5001(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test sitronix_1403_5001",
+ rdesc="05 0d 09 04 a1 01 85 01 09 54 95 01 75 08 81 02 09 22 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 05 01 26 90 04 75 0c 95 01 55 0f 65 11 a4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 02 09 48 09 49 81 02 c0 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 b4 a4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 02 09 48 09 49 81 02 c0 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 b4 a4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 02 09 48 09 49 81 02 c0 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 b4 a4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 02 09 48 09 49 81 02 c0 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 b4 a4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 02 09 48 09 49 81 02 c0 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 b4 a4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 02 09 48 09 49 81 02 c0 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 b4 a4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 02 09 48 09 49 81 02 c0 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 b4 a4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 02 09 48 09 49 81 02 c0 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 b4 a4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 02 09 48 09 49 81 02 c0 a1 02 09 51 75 06 95 01 81 02 09 42 09 32 15 00 25 01 75 01 95 02 81 02 b4 09 30 46 e1 00 81 02 26 50 03 09 31 45 7d 81 02 05 0d 75 08 95 04 09 48 09 49 81 02 c0 85 02 09 55 26 ff 00 75 08 95 01 b1 02 09 04 15 00 25 ff 75 08 95 07 91 02 c0 09 0e a1 01 85 03 09 23 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0",
+ input_info=(BusType.USB, 0x1403, 0x5001),
+ max_contacts=10,
+ )
+
+
+class TestSmart_0b8c_0092(BaseTest.TestMultitouch):
+ def create_device(self):
+ return SmartTechDigitizer(
+ "uhid test smart_0b8c_0092", input_info=(BusType.USB, 0x0B8C, 0x0092)
+ )
+
+
+class TestStantum_1f87_0002(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test stantum_1f87_0002",
+ rdesc="05 0d 09 04 a1 01 85 03 05 0d 09 54 95 01 75 08 81 02 06 00 ff 75 02 09 01 81 01 75 0e 09 02 81 02 05 0d 09 22 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 a1 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 30 81 02 05 0d 25 1f 75 05 09 48 81 02 05 01 16 00 00 26 ff 07 75 0b 55 00 65 00 09 31 81 02 05 0d 25 1f 75 05 09 49 81 02 75 08 09 51 95 01 81 02 09 30 75 05 81 02 09 42 15 00 25 01 75 01 95 01 81 02 09 47 81 02 09 32 81 02 c0 85 08 05 0d 09 55 95 01 75 08 25 0a b1 02 c0",
+ input_info=(BusType.USB, 0x1F87, 0x0002),
+ )
+
+
+class TestTopseed_1784_0016(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test topseed_1784_0016",
+ rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 04 75 10 55 00 65 00 09 30 35 00 46 ff 04 81 02 09 31 46 ff 04 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 0a 81 02 09 55 b1 02 c0 05 0c 09 01 a1 01 85 03 a1 02 09 b5 15 00 25 01 75 01 95 01 81 02 09 b6 81 02 09 b7 81 02 09 cd 81 02 09 e2 81 02 09 e9 81 02 09 ea 81 02 05 01 09 82 81 02 c0 c0",
+ input_info=(BusType.USB, 0x1784, 0x0016),
+ max_contacts=2,
+ )
+
+
+class TestTpv_25aa_8883(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test tpv_25aa_8883",
+ rdesc="05 01 09 02 a1 01 85 0d 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 05 0d 09 32 95 01 75 01 81 02 95 01 75 05 81 03 05 01 55 0e 65 11 75 10 95 01 35 00 46 98 12 26 7f 07 09 30 81 22 46 78 0a 26 37 04 09 31 81 22 35 00 45 00 15 81 25 7f 75 08 95 01 09 38 81 06 09 00 75 08 95 07 81 03 c0 c0 05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 7f 07 81 02 09 31 46 78 0a 26 37 04 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 75 10 55 0e 65 11 09 30 35 00 46 98 12 26 7f 07 81 02 46 78 0a 26 37 04 09 31 81 02 c0 05 0d 09 54 15 00 26 ff 00 95 01 75 08 81 02 09 55 25 02 95 01 85 02 b1 02 c0 05 0d 09 0e a1 01 06 00 ff 09 01 26 ff 00 75 08 95 47 85 03 b1 02 09 01 96 ff 03 85 04 b1 02 09 01 95 0b 85 05 b1 02 09 01 96 ff 03 85 06 b1 02 09 01 95 0f 85 07 b1 02 09 01 96 ff 03 85 08 b1 02 09 01 96 ff 03 85 09 b1 02 09 01 95 3f 85 0a b1 02 09 01 96 ff 03 85 0b b1 02 09 01 96 c3 03 85 0e b1 02 09 01 96 ff 03 85 0f b1 02 09 01 96 83 03 85 10 b1 02 09 01 96 93 00 85 11 b1 02 09 01 96 ff 03 85 12 b1 02 05 0d 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 85 0c b1 02 c0 c0",
+ input_info=(BusType.USB, 0x25AA, 0x8883),
+ )
+
+
+class TestTrs_star_238f_0001(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test trs-star_238f_0001",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 95 01 81 03 09 37 95 01 81 03 95 01 81 03 15 00 25 0f 75 04 09 51 95 01 81 02 09 54 95 01 81 02 09 55 95 01 81 02 05 01 26 ff 03 15 00 75 10 65 00 09 30 95 01 81 02 09 31 81 02 c0 05 0d 09 0e 85 02 09 23 a1 02 15 00 25 0a 09 52 75 08 95 01 b1 02 09 53 95 01 b1 02 09 55 95 01 b1 02 c0 c0",
+ input_info=(BusType.USB, 0x238F, 0x0001),
+ )
+
+
+class TestUnitec_227d_0103(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test unitec_227d_0103",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 16 00 00 26 ff 4f 36 00 00 46 6c 03 81 02 09 31 16 00 00 26 ff 3b 36 00 00 46 ed 01 81 02 26 00 00 46 00 00 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 16 00 00 26 ff 4f 36 00 00 46 6c 03 81 02 09 31 16 00 00 26 ff 3b 36 00 00 46 ed 01 81 02 26 00 00 46 00 00 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 16 00 00 26 ff 4f 36 00 00 46 6c 03 81 02 09 31 16 00 00 26 ff 3b 36 00 00 46 ed 01 81 02 26 00 00 46 00 00 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 16 00 00 26 ff 4f 36 00 00 46 6c 03 81 02 09 31 16 00 00 26 ff 3b 36 00 00 46 ed 01 81 02 26 00 00 46 00 00 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 35 00 55 0e 65 33 75 10 95 01 09 30 16 00 00 26 ff 4f 36 00 00 46 6c 03 81 02 09 31 16 00 00 26 ff 3b 36 00 00 46 ed 01 81 02 26 00 00 46 00 00 c0 05 0d 09 54 75 08 95 01 81 02 05 0d 85 03 09 55 25 05 75 08 95 01 b1 02 c0 05 0d 09 0e a1 01 85 04 09 53 15 00 25 05 75 08 95 01 b1 02 c0",
+ input_info=(BusType.USB, 0x227D, 0x0103),
+ )
+
+
+class TestZytronic_14c8_0005(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test zytronic_14c8_0005",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 95 01 81 02 95 06 81 01 05 01 26 00 10 75 10 95 01 65 00 09 30 81 02 09 31 46 00 10 81 02 05 0d 09 51 26 ff 00 75 08 95 01 81 02 c0 85 02 09 55 15 00 25 08 75 08 95 01 b1 02 c0 05 0d 09 0e a1 01 85 03 a1 02 09 23 09 52 09 53 15 00 25 08 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 15 00 26 00 10 35 00 46 00 10 65 00 75 10 95 02 81 62 c0 c0 06 00 ff 09 01 a1 01 85 05 09 00 15 00 26 ff 00 75 08 95 3f b1 02 c0 06 00 ff 09 01 a1 01 85 06 09 00 15 00 26 ff 00 75 08 95 3f 81 02 c0",
+ input_info=(BusType.USB, 0x14C8, 0x0005),
+ )
+
+
+class TestZytronic_14c8_0006(BaseTest.TestMultitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test zytronic_14c8_0006",
+ rdesc="05 0d 09 04 a1 01 85 01 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 09 30 81 02 09 31 81 02 05 0d c0 05 0d 09 54 95 01 75 08 15 00 25 3c 81 02 05 0d 85 02 09 55 95 01 75 08 15 00 25 3c b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 15 00 26 00 10 35 00 46 00 10 65 00 75 10 95 02 81 62 c0 c0 06 00 ff 09 01 a1 01 85 05 09 00 15 00 26 ff 00 75 08 95 3f b1 02 c0 06 00 ff 09 01 a1 01 85 06 09 00 15 00 26 ff 00 75 08 95 3f 81 02 c0",
+ input_info=(BusType.USB, 0x14C8, 0x0006),
+ )
+
+
+################################################################################
+#
+# Windows 8 compatible devices
+#
+################################################################################
+
+
+class TestMinWin8TSParallelTriple(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return MinWin8TSParallel(3)
+
+
+class TestMinWin8TSParallel(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return MinWin8TSParallel(10)
+
+
+class TestMinWin8TSHybrid(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return MinWin8TSHybrid()
+
+
+class TestWin8TSConfidence(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Win8TSConfidence(5)
+
+ @pytest.mark.skip_if_uhdev(
+ lambda uhdev: "Confidence" not in uhdev.fields,
+ "Device not compatible, missing Confidence usage",
+ )
+ def test_mt_confidence_bad_release(self):
+ """Check for the validity of the confidence bit.
+ When a contact is marked as not confident, it should be detected
+ as a palm from the kernel POV and released.
+
+ Note: if the kernel exports ABS_MT_TOOL_TYPE, it shouldn't release
+ the touch but instead convert it to ABS_MT_TOOL_PALM."""
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+
+ t0 = Touch(1, 150, 200)
+ r = uhdev.event([t0])
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+
+ t0.confidence = False
+ t0.tipswitch = False
+ r = uhdev.event([t0])
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+
+ if evdev.absinfo[libevdev.EV_ABS.ABS_MT_TOOL_TYPE] is not None:
+ # the kernel exports MT_TOOL_PALM
+ assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_TOOL_TYPE, 2) in events
+
+ assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+
+class TestElanXPS9360(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test ElanXPS9360",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 a4 26 20 0d 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 50 07 46 a6 00 09 31 81 02 b4 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 0a 09 55 25 0a b1 02 85 44 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 ff 01 09 01 a1 01 85 02 15 00 26 ff 00 75 08 95 40 09 00 81 02 c0 06 00 ff 09 01 a1 01 85 03 75 08 95 1f 09 01 91 02 c0 06 01 ff 09 01 a1 01 85 04 15 00 26 ff 00 75 08 95 13 09 00 81 02 c0",
+ )
+
+
+class TestTouchpadXPS9360(BaseTest.TestPTP):
+ def create_device(self):
+ return PTP(
+ "uhid test TouchpadXPS9360",
+ max_contacts=5,
+ rdesc="05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 01 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0 05 0d 09 05 a1 01 85 03 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 c0 04 75 10 55 0e 65 11 09 30 35 00 46 f5 03 95 01 81 02 46 36 02 26 a8 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 c0 04 75 10 55 0e 65 11 09 30 35 00 46 f5 03 95 01 81 02 46 36 02 26 a8 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 c0 04 75 10 55 0e 65 11 09 30 35 00 46 f5 03 95 01 81 02 46 36 02 26 a8 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 c0 04 75 10 55 0e 65 11 09 30 35 00 46 f5 03 95 01 81 02 46 36 02 26 a8 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 c0 04 75 10 55 0e 65 11 09 30 35 00 46 f5 03 95 01 81 02 46 36 02 26 a8 02 09 31 81 02 c0 05 0d 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 25 7f 95 01 75 08 81 02 05 09 09 01 25 01 75 01 95 01 81 02 95 07 81 03 05 0d 85 08 09 55 09 59 75 04 95 02 25 0f b1 02 85 0d 09 60 75 01 95 01 15 00 25 01 b1 02 95 07 b1 03 85 07 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 0e a1 01 85 04 09 22 a1 02 09 52 15 00 25 0a 75 08 95 01 b1 02 c0 09 22 a1 00 85 06 09 57 09 58 75 01 95 02 25 01 b1 02 95 06 b1 03 c0 c0 06 00 ff 09 01 a1 01 85 09 09 02 15 00 26 ff 00 75 08 95 14 91 02 85 0a 09 03 15 00 26 ff 00 75 08 95 14 91 02 85 0b 09 04 15 00 26 ff 00 75 08 95 3d 81 02 85 0c 09 05 15 00 26 ff 00 75 08 95 3d 81 02 85 0f 09 06 15 00 26 ff 00 75 08 95 03 b1 02 85 0e 09 07 15 00 26 ff 00 75 08 95 01 b1 02 c0",
+ )
+
+
+class TestSurfaceBook2(BaseTest.TestPTP):
+ def create_device(self):
+ return PTP(
+ "uhid test SurfaceBook2",
+ max_contacts=5,
+ rdesc="05 01 09 06 A1 01 85 01 14 25 01 75 01 95 08 05 07 19 E0 29 E7 81 02 75 08 95 0A 18 29 91 26 FF 00 80 05 0C 0A C0 02 A1 02 1A C1 02 2A C6 02 95 06 B1 03 C0 05 08 19 01 29 03 75 01 95 03 25 01 91 02 95 05 91 01 C0 05 01 09 02 A1 01 85 02 05 09 19 01 29 05 81 02 95 01 75 03 81 03 15 81 25 7F 75 08 95 02 05 01 09 30 09 31 81 06 A1 02 09 48 14 25 01 35 01 45 10 75 02 95 01 A4 B1 02 09 38 15 81 25 7F 34 44 75 08 81 06 C0 A1 02 09 48 B4 B1 02 34 44 75 04 B1 03 05 0C 0A 38 02 15 81 25 7F 75 08 81 06 C0 C0 05 0C 09 01 A1 01 85 03 75 10 14 26 FF 03 18 2A FF 03 80 C0 06 05 FF 09 01 A1 01 85 0D 25 FF 95 02 75 08 09 20 81 02 09 22 91 02 15 81 25 7F 95 20 75 08 09 21 81 02 09 23 91 02 C0 09 02 A1 01 85 0C 14 25 FF 95 01 08 91 02 C0 05 0D 09 05 A1 01 85 04 09 22 A1 02 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 03 09 51 81 02 75 01 95 03 81 03 05 01 26 E4 07 75 10 55 0E 65 11 09 30 46 F2 03 95 01 81 02 46 94 02 26 29 05 09 31 81 02 44 54 64 C0 05 0D 09 22 A1 02 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 03 09 51 81 02 75 01 95 03 81 03 05 01 26 E4 07 75 10 55 0E 65 11 09 30 46 F2 03 95 01 81 02 46 94 02 26 29 05 09 31 81 02 44 54 64 C0 05 0D 09 22 A1 02 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 03 09 51 81 02 75 01 95 03 81 03 05 01 26 E4 07 75 10 55 0E 65 11 09 30 46 F2 03 95 01 81 02 46 94 02 26 29 05 09 31 81 02 C0 05 0D 09 22 A1 02 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 03 09 51 81 02 75 01 95 03 81 03 05 01 26 E4 07 75 10 55 0E 65 11 09 30 46 F2 03 95 01 81 02 46 94 02 26 29 05 09 31 81 02 44 54 64 C0 05 0D 09 22 A1 02 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 03 09 51 81 02 75 01 95 03 81 03 05 01 26 E4 07 75 10 55 0E 65 11 09 30 46 F2 03 95 01 81 02 46 94 02 26 29 05 09 31 81 02 C0 05 0D 55 0C 66 01 10 47 FF FF 00 00 27 FF FF 00 00 09 56 81 02 09 54 25 7F 75 08 81 02 05 09 09 01 25 01 75 01 81 02 95 07 81 03 05 0D 85 04 09 55 09 59 75 04 95 02 25 0F B1 02 06 00 FF 09 C6 85 05 14 25 08 75 08 95 01 B1 02 09 C7 26 FF 00 75 08 95 20 B1 02 C0 05 0D 09 0E A1 01 85 07 09 22 A1 02 09 52 14 25 0A 75 08 95 01 B1 02 C0 09 22 A0 85 08 09 57 09 58 75 01 95 02 25 01 B1 02 95 06 B1 03 C0 C0 06 07 FF 09 01 A1 01 85 0A 09 02 26 FF 00 75 08 95 14 91 02 85 09 09 03 91 02 85 0A 09 04 95 26 81 02 85 09 09 05 81 02 85 09 09 06 95 01 B1 02 85 0B 09 07 B1 02 C0 06 05 FF 09 04 A1 01 85 0E 09 31 91 02 09 31 81 03 09 30 91 02 09 30 81 02 95 39 09 32 92 02 01 09 32 82 02 01 C0 06 05 FF 09 50 A1 01 85 20 14 25 FF 75 08 95 3C 09 60 82 02 01 09 61 92 02 01 09 62 B2 02 01 85 21 09 63 82 02 01 09 64 92 02 01 09 65 B2 02 01 85 22 25 FF 75 20 95 04 19 66 29 69 81 02 19 6A 29 6D 91 02 19 6E 29 71 B1 02 85 23 19 72 29 75 81 02 19 76 29 79 91 02 19 7A 29 7D B1 02 85 24 19 7E 29 81 81 02 19 82 29 85 91 02 19 86 29 89 B1 02 85 25 19 8A 29 8D 81 02 19 8E 29 91 91 02 19 92 29 95 B1 02 85 26 19 96 29 99 81 02 19 9A 29 9D 91 02 19 9E 29 A1 B1 02 85 27 19 A2 29 A5 81 02 19 A6 29 A9 91 02 19 AA 29 AD B1 02 85 28 19 AE 29 B1 81 02 19 B2 29 B5 91 02 19 B6 29 B9 B1 02 85 29 19 BA 29 BD 81 02 19 BE 29 C1 91 02 19 C2 29 C5 B1 02 C0 06 00 FF 0A 00 F9 A1 01 85 32 75 10 95 02 14 27 FF FF 00 00 0A 01 F9 B1 02 75 20 95 01 25 FF 0A 02 F9 B1 02 75 08 95 08 26 FF 00 0A 03 F9 B2 02 01 95 10 0A 04 F9 B2 02 01 0A 05 F9 B2 02 01 95 01 75 10 27 FF FF 00 00 0A 06 F9 81 02 C0",
+ )
+
+
+class Test3m_0596_051c(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test 3m_0596_051c",
+ rdesc="05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 09 01 95 01 75 01 15 00 25 01 81 02 95 07 75 01 81 03 95 01 75 08 81 03 05 01 09 30 09 31 15 00 26 ff 7f 35 00 46 ff 7f 95 02 75 10 81 02 c0 a1 02 15 00 26 ff 00 09 01 95 39 75 08 81 03 c0 c0 05 0d 09 0e a1 01 85 11 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 09 04 a1 01 85 13 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 81 03 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 d1 12 81 02 09 31 46 b2 0b 81 02 06 00 ff 75 10 95 02 09 01 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 81 03 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 d1 12 81 02 09 31 46 b2 0b 81 02 06 00 ff 75 10 95 02 09 01 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 81 03 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 d1 12 81 02 09 31 46 b2 0b 81 02 06 00 ff 75 10 95 02 09 01 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 81 03 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 d1 12 81 02 09 31 46 b2 0b 81 02 06 00 ff 75 10 95 02 09 01 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 81 03 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 d1 12 81 02 09 31 46 b2 0b 81 02 06 00 ff 75 10 95 02 09 01 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 81 03 09 47 81 02 95 05 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 d1 12 81 02 09 31 46 b2 0b 81 02 06 00 ff 75 10 95 02 09 01 81 02 c0 05 0d 09 54 95 01 75 08 15 00 25 14 81 02 05 0d 55 0c 66 01 10 35 00 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 05 0d 09 55 85 12 15 00 25 14 75 08 95 01 b1 02 85 44 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 06 00 ff 15 00 26 ff 00 85 03 09 01 75 08 95 07 b1 02 85 04 09 01 75 08 95 17 b1 02 85 05 09 01 75 08 95 47 b1 02 85 06 09 01 75 08 95 07 b1 02 85 73 09 01 75 08 95 07 b1 02 85 08 09 01 75 08 95 07 b1 02 85 09 09 01 75 08 95 3f b1 02 85 0f 09 01 75 08 96 07 02 b1 02 c0",
+ )
+
+
+class Testadvanced_silicon_04e8_2084(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test advanced_silicon_04e8_2084",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 c0 14 81 02 46 ae 0b 09 31 81 02 45 00 c0 05 0d 15 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 25 0a 75 08 09 54 81 02 85 44 09 55 b1 02 85 44 06 00 ff 09 c5 26 ff 00 96 00 01 b1 02 85 f0 09 01 95 04 b1 02 85 f2 09 03 b1 02 09 04 b1 02 09 05 b1 02 95 01 09 06 b1 02 09 07 b1 02 85 f1 09 02 95 07 91 02 85 f3 09 08 95 3d b1 02 c0",
+ )
+
+
+class Testadvanced_silicon_2149_2306(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test advanced_silicon_2149_2306",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 15 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 25 0a 75 08 09 54 81 02 85 44 09 55 b1 02 85 44 06 00 ff 09 c5 26 ff 00 96 00 01 b1 02 85 f0 09 01 95 04 81 02 85 f2 09 03 b1 02 09 04 b1 02 09 05 b1 02 95 01 09 06 b1 02 09 07 b1 02 85 f1 09 02 95 07 91 02 85 f3 09 08 95 3d b1 02 c0",
+ )
+
+
+class Testadvanced_silicon_2149_230a(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test advanced_silicon_2149_230a",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f6 13 81 02 46 40 0b 09 31 81 02 45 00 c0 05 0d 15 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 25 0a 75 08 09 54 81 02 85 44 09 55 b1 02 85 44 06 00 ff 09 c5 26 ff 00 96 00 01 b1 02 85 f0 09 01 95 04 81 02 85 f2 09 03 b1 02 09 04 b1 02 09 05 b1 02 95 01 09 06 b1 02 09 07 b1 02 85 f1 09 02 95 07 91 02 85 f3 09 08 95 3d b1 02 c0",
+ )
+
+
+class Testadvanced_silicon_2149_231c(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test advanced_silicon_2149_231c",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 e2 13 81 02 46 32 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 e2 13 81 02 46 32 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 e2 13 81 02 46 32 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 e2 13 81 02 46 32 0b 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 e2 13 81 02 46 32 0b 09 31 81 02 45 00 c0 05 0d 15 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 25 0a 75 08 09 54 81 02 85 44 09 55 b1 02 85 44 06 00 ff 09 c5 26 ff 00 96 00 01 b1 02 85 f0 09 01 95 04 b1 02 85 f2 09 03 b1 02 09 04 b1 02 09 05 b1 02 95 01 09 06 b1 02 09 07 b1 02 85 f1 09 02 95 07 91 02 85 f3 09 08 95 3d b1 02 c0",
+ )
+
+
+class Testadvanced_silicon_2149_2703(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test advanced_silicon_2149_2703",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 66 17 81 02 46 34 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 66 17 81 02 46 34 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 66 17 81 02 46 34 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 66 17 81 02 46 34 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 66 17 81 02 46 34 0d 09 31 81 02 45 00 c0 05 0d 15 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 25 0a 75 08 09 54 81 02 85 44 09 55 b1 02 85 44 06 00 ff 09 c5 26 ff 00 96 00 01 b1 02 85 f0 09 01 95 04 81 02 85 f2 09 03 b1 02 09 04 b1 02 09 05 b1 02 95 01 09 06 b1 02 09 07 b1 02 85 f1 09 02 95 07 91 02 85 f3 09 08 95 3d b1 02 c0",
+ )
+
+
+class Testadvanced_silicon_2149_270b(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test advanced_silicon_2149_270b",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 52 17 81 02 46 20 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 52 17 81 02 46 20 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 52 17 81 02 46 20 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 52 17 81 02 46 20 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 52 17 81 02 46 20 0d 09 31 81 02 45 00 c0 05 0d 15 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 25 0a 75 08 09 54 81 02 85 44 09 55 b1 02 85 44 06 00 ff 09 c5 26 ff 00 96 00 01 b1 02 85 f0 09 01 95 04 b1 02 85 f2 09 03 b1 02 09 04 b1 02 09 05 b1 02 95 01 09 06 b1 02 09 07 b1 02 85 f1 09 02 95 07 91 02 85 f3 09 08 95 3d b1 02 c0",
+ )
+
+
+class Testadvanced_silicon_2575_0204(BaseTest.TestWin8Multitouch):
+ """found on the Dell Canvas 27"""
+
+ def create_device(self):
+ return Digitizer(
+ "uhid test advanced_silicon_2575_0204",
+ rdesc="05 0d 09 04 a1 01 85 01 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 51 75 07 95 01 81 02 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 4f 17 81 02 46 1d 0d 09 31 81 02 45 00 c0 05 0d 15 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 25 0a 75 08 09 54 81 02 85 42 09 55 25 0a b1 02 85 44 06 00 ff 09 c5 26 ff 00 96 00 01 b1 02 c0 05 01 09 0e a1 01 85 05 05 01 09 08 a1 00 09 30 55 0e 65 11 15 00 26 ff 7f 35 00 46 4f 17 75 10 95 01 81 42 09 31 46 1d 0d 81 42 06 00 ff 09 01 75 20 81 03 05 01 09 37 55 00 65 14 16 98 fe 26 68 01 36 98 fe 46 68 01 75 0f 81 06 05 09 09 01 65 00 15 00 25 01 35 00 45 00 75 01 81 02 05 0d 09 42 81 02 09 51 75 07 25 7f 81 02 05 0d 09 48 55 0e 65 11 15 00 26 ff 7f 35 00 46 ff 7f 75 10 81 02 09 49 81 02 09 3f 55 00 65 14 15 00 26 67 01 35 00 46 67 01 81 0a c0 65 00 35 00 45 00 05 0d 15 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 25 05 75 08 09 54 81 02 85 47 09 55 25 05 b1 02 c0 06 00 ff 09 04 a1 01 85 f0 09 01 75 08 95 04 b1 02 85 f2 09 03 b1 02 09 04 b1 02 09 05 b1 02 85 c0 09 01 95 03 b1 02 85 c2 09 01 95 0f b1 02 85 c4 09 01 95 3e b1 02 85 c5 09 01 95 7e b1 02 85 c6 09 01 95 fe b1 02 85 c8 09 01 96 fe 03 b1 02 85 0a 09 01 95 3f b1 02 c0",
+ )
+
+
+class Testadvanced_silicon_2619_5610(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test advanced_silicon_2619_5610",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 02 81 03 09 51 25 1f 75 05 95 01 81 02 a1 00 05 01 26 ff 7f 75 10 55 0e 65 11 09 30 35 00 46 f9 15 81 02 46 73 0c 09 31 81 02 45 00 c0 c0 05 0d 15 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 25 0a 75 08 09 54 81 02 85 44 09 55 b1 02 85 44 06 00 ff 09 c5 26 ff 00 96 00 01 b1 02 85 f0 09 01 95 04 81 02 85 f2 09 03 b1 02 09 04 b1 02 09 05 b1 02 95 01 09 06 b1 02 09 07 b1 02 85 f1 09 02 95 07 91 02 c0",
+ )
+
+
+class Testatmel_03eb_8409(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test atmel_03eb_8409",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 05 0d 27 ff ff 00 00 75 10 95 01 09 56 81 02 15 00 25 1f 75 05 09 54 95 01 81 02 75 03 25 01 95 01 81 03 75 08 85 02 09 55 25 10 b1 02 06 00 ff 85 05 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 00 a1 01 85 03 09 20 a1 00 15 00 25 01 75 01 95 01 09 42 81 02 09 44 81 02 09 45 81 02 81 03 09 32 81 02 95 03 81 03 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 46 18 06 26 77 0f 09 31 81 02 05 0d 09 30 15 01 26 ff 00 75 08 95 01 81 02 c0 c0",
+ )
+
+
+class Testatmel_03eb_840b(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test atmel_03eb_840b",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 05 0d 27 ff ff 00 00 75 10 95 01 09 56 81 02 15 00 25 1f 75 05 09 54 95 01 81 02 75 03 25 01 95 01 81 03 75 08 85 02 09 55 25 10 b1 02 06 00 ff 85 05 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 02 a1 01 85 03 09 20 a1 00 15 00 25 01 75 01 95 01 09 42 81 02 09 44 81 02 09 45 81 02 81 03 09 32 81 02 95 03 81 03 05 01 55 0e 65 11 35 00 75 10 95 02 46 00 0a 26 ff 0f 09 30 81 02 46 a0 05 26 ff 0f 09 31 81 02 05 0d 09 30 15 01 26 ff 00 75 08 95 01 81 02 c0 c0",
+ )
+
+
+class Testdell_044e_1220(BaseTest.TestPTP):
+ def create_device(self):
+ return PTP(
+ "uhid test dell_044e_1220",
+ type="pressurepad",
+ rdesc="05 01 09 02 a1 01 85 01 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 75 01 95 03 81 02 95 05 81 01 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 09 38 95 01 81 06 05 0c 0a 38 02 81 06 c0 c0 05 0d 09 05 a1 01 85 08 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 af 04 75 10 55 0e 65 11 09 30 35 00 46 e8 03 95 01 81 02 26 7b 02 46 12 02 09 31 81 02 c0 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 05 0d 09 56 81 02 09 54 25 05 95 01 75 08 81 02 05 09 19 01 29 03 25 01 75 01 95 03 81 02 95 05 81 03 05 0d 85 09 09 55 75 08 95 01 25 05 b1 02 06 00 ff 85 0a 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 01 ff 09 01 a1 01 85 03 09 01 15 00 26 ff 00 95 1b 81 02 85 04 09 02 95 50 81 02 85 05 09 03 95 07 b1 02 85 06 09 04 81 02 c0 06 02 ff 09 01 a1 01 85 07 09 02 95 86 75 08 b1 02 c0 05 0d 09 0e a1 01 85 0b 09 22 a1 02 09 52 15 00 25 0a 75 08 95 01 b1 02 c0 09 22 a1 00 85 0c 09 57 09 58 75 01 95 02 25 01 b1 02 95 06 b1 03 c0 c0",
+ )
+
+
+class Testdell_06cb_75db(BaseTest.TestPTP):
+ def create_device(self):
+ return PTP(
+ "uhid test dell_06cb_75db",
+ max_contacts=3,
+ rdesc="05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 01 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0 05 0d 09 05 a1 01 85 03 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 c8 04 75 10 55 0e 65 11 09 30 35 00 46 fb 03 95 01 81 02 46 6c 02 26 e8 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 c8 04 75 10 55 0e 65 11 09 30 35 00 46 fb 03 95 01 81 02 46 6c 02 26 e8 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 c8 04 75 10 55 0e 65 11 09 30 35 00 46 fb 03 95 01 81 02 46 6c 02 26 e8 02 09 31 81 02 05 0d c0 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 25 7f 95 01 75 08 81 02 05 09 09 01 25 01 75 01 95 01 81 02 95 07 81 03 05 0d 85 08 09 55 09 59 75 04 95 02 25 0f b1 02 85 0d 09 60 75 01 95 01 15 00 25 01 b1 02 95 07 b1 03 85 07 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 0e a1 01 85 04 09 22 a1 02 09 52 15 00 25 0a 75 08 95 01 b1 02 c0 09 22 a1 00 85 06 09 57 09 58 75 01 95 02 25 01 b1 02 95 06 b1 03 c0 c0 06 00 ff 09 01 a1 01 85 09 09 02 15 00 26 ff 00 75 08 95 14 91 02 85 0a 09 03 15 00 26 ff 00 75 08 95 14 91 02 85 0b 09 04 15 00 26 ff 00 75 08 95 1a 81 02 85 0c 09 05 15 00 26 ff 00 75 08 95 1a 81 02 85 0f 09 06 15 00 26 ff 00 75 08 95 01 b1 02 85 0e 09 07 15 00 26 ff 00 75 08 95 01 b1 02 c0",
+ )
+
+
+class Testegalax_capacitive_0eef_790a(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test egalax_capacitive_0eef_790a",
+ max_contacts=10,
+ rdesc="05 0d 09 04 a1 01 85 06 05 0d 09 54 75 08 15 00 25 0c 95 01 81 02 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 15 00 25 20 81 02 05 01 26 ff 0f 75 10 55 0e 65 11 09 30 35 00 46 13 0c 81 02 46 cb 06 09 31 81 02 75 08 95 02 81 03 81 03 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 15 00 25 20 81 02 05 01 26 ff 0f 75 10 55 0e 65 11 09 30 35 00 46 13 0c 81 02 46 cb 06 09 31 81 02 75 08 95 02 81 03 81 03 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 15 00 25 20 81 02 05 01 26 ff 0f 75 10 55 0e 65 11 09 30 35 00 46 13 0c 81 02 46 cb 06 09 31 81 02 75 08 95 02 81 03 81 03 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 15 00 25 20 81 02 05 01 26 ff 0f 75 10 55 0e 65 11 09 30 35 00 46 13 0c 81 02 46 cb 06 09 31 81 02 75 08 95 02 81 03 81 03 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 15 00 25 20 81 02 05 01 26 ff 0f 75 10 55 0e 65 11 09 30 35 00 46 13 0c 81 02 46 cb 06 09 31 81 02 75 08 95 02 81 03 81 03 c0 05 0d 17 00 00 00 00 27 ff ff ff 7f 75 20 95 01 55 00 65 00 09 56 81 02 09 55 09 53 75 08 95 02 26 ff 00 b1 02 06 00 ff 09 c5 85 07 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 01 a1 01 85 01 09 01 a1 02 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 0e a1 01 85 05 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0",
+ )
+
+
+class Testelan_04f3_000a(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test elan_04f3_000a",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 00 08 46 a6 00 09 31 81 02 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 0a 09 55 25 0a b1 02 85 44 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 ff 01 09 01 a1 01 85 02 15 00 26 ff 00 75 08 95 40 09 00 81 02 c0 06 00 ff 09 01 a1 01 85 03 75 08 95 1f 09 01 91 02 c0 06 01 ff 09 01 a1 01 85 04 15 00 26 ff 00 75 08 95 13 09 00 81 02 c0",
+ )
+
+
+class Testelan_04f3_000c(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test elan_04f3_000c",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 40 0e 75 10 55 0f 65 11 09 30 35 00 46 01 01 95 02 81 02 26 00 08 46 91 00 09 31 81 02 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 0a 09 55 25 0a b1 02 85 44 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 ff 01 09 01 a1 01 85 02 15 00 26 ff 00 75 08 95 40 09 00 81 02 c0 06 00 ff 09 01 a1 01 85 03 75 08 95 1f 09 01 91 02 c0 06 01 ff 09 01 a1 01 85 04 15 00 26 ff 00 75 08 95 13 09 00 81 02 c0",
+ )
+
+
+class Testelan_04f3_010c(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test elan_04f3_010c",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c2 00 09 31 81 02 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 0a 09 55 25 0a b1 02 85 44 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 ff 01 09 01 a1 01 85 02 15 00 26 ff 00 75 08 95 40 09 00 81 02 c0 06 00 ff 09 01 a1 01 85 03 75 08 95 1f 09 01 91 02 c0 06 01 ff 09 01 a1 01 85 04 15 00 26 ff 00 75 08 95 13 09 00 81 02 c0",
+ )
+
+
+class Testelan_04f3_0125(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test elan_04f3_0125",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 f0 0c 75 10 55 0f 65 11 09 30 35 00 46 58 01 95 02 81 02 26 50 07 46 c1 00 09 31 81 02 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 0a 09 55 25 0a b1 02 85 44 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 ff 01 09 01 a1 01 85 02 15 00 26 ff 00 75 08 95 40 09 00 81 02 c0 06 00 ff 09 01 a1 01 85 03 75 08 95 1f 09 01 91 02 c0 06 01 ff 09 01 a1 01 85 04 15 00 26 ff 00 75 08 95 13 09 00 81 02 c0",
+ )
+
+
+class Testelan_04f3_016f(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test elan_04f3_016f",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 0a 09 55 25 0a b1 02 85 44 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 ff 01 09 01 a1 01 85 02 15 00 26 ff 00 75 08 95 40 09 00 81 02 c0 06 00 ff 09 01 a1 01 85 03 75 08 95 1f 09 01 91 02 c0 06 01 ff 09 01 a1 01 85 04 15 00 26 ff 00 75 08 95 13 09 00 81 02 c0",
+ )
+
+
+class Testelan_04f3_0732(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test elan_04f3_0732",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0b 75 10 55 0f 65 11 09 30 35 00 46 ff 00 95 02 81 02 26 40 07 46 85 00 09 31 81 02 c0 05 0d 09 56 55 00 65 00 27 ff ff 00 00 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 0a 09 55 25 0a b1 02 85 44 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 ff 01 09 01 a1 01 85 02 15 00 25 ff 75 08 95 40 09 00 81 02 c0 06 00 ff 09 01 a1 01 85 03 75 08 95 1f 09 01 91 02 c0",
+ )
+
+
+class Testelan_04f3_200a(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test elan_04f3_200a",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 75 06 09 51 25 3f 81 02 26 ff 00 75 08 09 48 81 02 09 49 81 02 95 01 05 01 26 c0 0e 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 02 81 02 26 40 08 46 a6 00 09 31 81 02 c0 05 0d 09 56 55 00 65 00 27 ff ff 00 00 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 0a 09 55 25 0a b1 02 85 0e 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0",
+ )
+
+
+class Testelan_04f3_300b(BaseTest.TestPTP):
+ def create_device(self):
+ return PTP(
+ "uhid test elan_04f3_300b",
+ max_contacts=3,
+ rdesc="05 01 09 02 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 09 38 15 81 25 7f 75 08 95 03 81 06 05 0c 0a 38 02 95 01 81 06 75 08 95 03 81 03 c0 06 00 ff 85 0d 09 c5 15 00 26 ff 00 75 08 95 04 b1 02 85 0c 09 c6 96 76 02 75 08 b1 02 85 0b 09 c7 95 42 75 08 b1 02 09 01 85 5d 95 1f 75 08 81 06 c0 05 0d 09 05 a1 01 85 04 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 02 25 02 09 51 81 02 75 01 95 04 81 03 05 01 15 00 26 a7 0c 75 10 55 0e 65 13 09 30 35 00 46 9d 01 95 01 81 02 46 25 01 26 2b 09 26 2b 09 09 31 81 02 05 0d 15 00 25 64 95 03 c0 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 25 7f 95 01 75 08 81 02 05 09 09 01 25 01 75 01 95 01 81 02 95 07 81 03 05 0d 85 02 09 55 09 59 75 04 95 02 25 0f b1 02 85 07 09 60 75 01 95 01 15 00 25 01 b1 02 95 0f b1 03 06 00 ff 06 00 ff 85 06 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 0e a1 01 85 03 09 22 a1 00 09 52 15 00 25 0a 75 08 95 02 b1 02 c0 09 22 a1 00 85 05 09 57 09 58 15 00 75 01 95 02 25 03 b1 02 95 0e b1 03 c0 c0",
+ )
+
+
+class Testelan_04f3_3045(BaseTest.TestPTP):
+ def create_device(self):
+ return PTP(
+ "uhid test elan_04f3_3045",
+ rdesc="05 01 09 02 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 09 38 15 81 25 7f 75 08 95 03 81 06 05 0c 0a 38 02 95 01 81 06 75 08 95 03 81 03 c0 c0 05 0d 09 05 a1 01 85 04 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 75 01 95 02 81 03 95 01 75 04 25 0f 09 51 81 02 05 01 15 00 26 80 0c 75 10 55 0e 65 13 09 30 35 00 46 90 01 95 01 81 02 46 13 01 26 96 08 26 96 08 09 31 81 02 05 0d 15 00 25 64 95 03 c0 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 25 7f 95 01 75 08 81 02 05 09 09 01 25 01 75 01 95 01 81 02 95 07 81 03 09 c5 75 08 95 04 81 03 05 0d 85 02 09 55 09 59 75 04 95 02 25 0f b1 02 85 07 09 60 75 01 95 01 15 00 25 01 b1 02 95 0f b1 03 06 00 ff 06 00 ff 85 06 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 85 0d 09 c5 15 00 26 ff 00 75 08 95 04 b1 02 85 0c 09 c6 96 8a 02 75 08 b1 02 85 0b 09 c7 95 80 75 08 b1 02 c0 05 0d 09 0e a1 01 85 03 09 22 a1 00 09 52 15 00 25 0a 75 08 95 02 b1 02 c0 09 22 a1 00 85 05 09 57 09 58 15 00 75 01 95 02 25 03 b1 02 95 0e b1 03 c0 c0",
+ )
+
+
+class Testelan_04f3_313a(BaseTest.TestPTP):
+ def create_device(self):
+ return PTP(
+ "uhid test elan_04f3_313a",
+ type="touchpad",
+ input_info=(BusType.I2C, 0x04F3, 0x313A),
+ rdesc="05 01 09 02 a1 01 85 01 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 75 01 95 03 81 02 95 05 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 75 08 95 05 81 03 c0 06 00 ff 09 01 85 0e 09 c5 15 00 26 ff 00 75 08 95 04 b1 02 85 0a 09 c6 15 00 26 ff 00 75 08 95 04 b1 02 c0 06 00 ff 09 01 a1 01 85 5c 09 01 95 0b 75 08 81 06 85 0d 09 c5 15 00 26 ff 00 75 08 95 04 b1 02 85 0c 09 c6 96 80 03 75 08 b1 02 85 0b 09 c7 95 82 75 08 b1 02 c0 05 0d 09 05 a1 01 85 04 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 05 09 09 02 09 03 15 00 25 01 75 01 95 02 81 02 05 0d 95 01 75 04 25 0f 09 51 81 02 05 01 15 00 26 d7 0e 75 10 55 0d 65 11 09 30 35 00 46 44 2f 95 01 81 02 46 12 16 26 eb 06 26 eb 06 09 31 81 02 05 0d 15 00 25 64 95 03 c0 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 25 7f 95 01 75 08 81 02 25 01 75 01 95 08 81 03 09 c5 75 08 95 02 81 03 05 0d 85 02 09 55 09 59 75 04 95 02 25 0f b1 02 85 07 09 60 75 01 95 01 15 00 25 01 b1 02 95 0f b1 03 06 00 ff 06 00 ff 85 06 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 0e a1 01 85 03 09 22 a1 00 09 52 15 00 25 0a 75 10 95 01 b1 02 c0 09 22 a1 00 85 05 09 57 09 58 75 01 95 02 25 01 b1 02 95 0e b1 03 c0 c0 05 01 09 02 a1 01 85 2a 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 75 01 95 03 81 02 95 05 81 03 05 01 09 30 09 31 15 81 25 7f 35 81 45 7f 55 00 65 13 75 08 95 02 81 06 75 08 95 05 81 03 c0 c0",
+ )
+
+
+class Testelo_04e7_0080(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test elo_04e7_0080",
+ rdesc="05 0d 09 04 a1 01 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 75 10 09 30 46 7c 24 81 02 09 31 46 96 14 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 75 10 09 30 46 7c 24 81 02 09 31 46 96 14 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 75 10 09 30 46 7c 24 81 02 09 31 46 96 14 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 03 81 03 09 32 95 02 81 02 95 02 81 03 09 51 75 08 95 01 81 02 05 01 26 ff 7f 65 11 55 0e 75 10 09 30 46 7c 24 81 02 09 31 46 96 14 81 02 c0 05 0d 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 75 08 95 01 15 00 25 08 81 02 09 55 b1 02 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0",
+ )
+
+
+class Testilitek_222a_0015(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test ilitek_222a_0015",
+ rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 c2 16 35 00 46 b3 08 81 42 09 31 26 c2 0c 46 e4 04 81 42 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 02 09 55 25 0a b1 02 06 00 ff 09 c5 85 06 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 00 ff 09 01 a1 01 09 01 85 03 15 00 26 ff 00 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0",
+ )
+
+
+class Testilitek_222a_001c(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test ilitek_222a_001c",
+ rdesc="05 0d 09 04 a1 01 85 04 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 74 1d 35 00 46 70 0d 81 42 09 31 26 74 10 46 8f 07 81 42 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 85 02 09 55 25 0a b1 02 06 00 ff 09 c5 85 06 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 00 ff 09 01 a1 01 09 01 85 03 15 00 26 ff 00 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0",
+ )
+
+
+class Testite_06cb_2968(BaseTest.TestPTP):
+ def create_device(self):
+ return PTP(
+ "uhid test ite_06cb_2968",
+ rdesc="05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 01 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0 05 0d 09 05 a1 01 85 03 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 1b 04 75 10 55 0e 65 11 09 30 35 00 46 6c 03 95 01 81 02 46 db 01 26 3b 02 09 31 81 02 05 0d c0 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 25 7f 95 01 75 08 81 02 05 09 09 01 25 01 75 01 95 01 81 02 95 07 81 03 05 0d 85 08 09 55 09 59 75 04 95 02 25 0f b1 02 85 0d 09 60 75 01 95 01 15 00 25 01 b1 02 95 07 b1 03 85 07 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 0e a1 01 85 04 09 22 a1 02 09 52 15 00 25 0a 75 08 95 01 b1 02 c0 09 22 a1 00 85 06 09 57 09 58 75 01 95 02 25 01 b1 02 95 06 b1 03 c0 c0 06 00 ff 09 01 a1 01 85 09 09 02 15 00 26 ff 00 75 08 95 14 91 02 85 0a 09 03 15 00 26 ff 00 75 08 95 14 91 02 85 0b 09 04 15 00 26 ff 00 75 08 95 1a 81 02 85 0c 09 05 15 00 26 ff 00 75 08 95 1a 81 02 85 0f 09 06 15 00 26 ff 00 75 08 95 01 b1 02 85 0e 09 07 15 00 26 ff 00 75 08 95 01 b1 02 c0",
+ max_contacts=5,
+ input_info=(0x3, 0x06CB, 0x2968),
+ )
+
+
+class Testn_trig_1b96_0c01(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test n_trig_1b96_0c01",
+ rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0",
+ )
+
+
+class Testn_trig_1b96_0c03(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test n_trig_1b96_0c03",
+ rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0",
+ )
+
+
+class Testn_trig_1b96_0f00(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test n_trig_1b96_0f00",
+ rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0",
+ )
+
+
+class Testn_trig_1b96_0f04(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test n_trig_1b96_0f04",
+ rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 7f 0b 26 80 25 81 02 09 31 46 78 06 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 7f 0b 26 80 25 81 02 09 31 46 78 06 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 7f 0b 26 80 25 81 02 09 31 46 78 06 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0",
+ )
+
+
+class Testn_trig_1b96_1000(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test n_trig_1b96_1000",
+ rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0",
+ )
+
+
+class Testsharp_04dd_9681(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test sharp_04dd_9681",
+ rdesc="06 00 ff 09 01 a1 01 75 08 26 ff 00 15 00 85 06 95 3f 09 01 91 02 85 05 95 3f 09 01 81 02 c0 05 0d 09 04 a1 01 85 81 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 81 02 05 01 65 11 55 0f 35 00 46 b0 01 26 80 07 75 10 09 30 81 02 46 f3 00 26 38 04 09 31 81 02 05 0d 09 48 09 49 26 ff 00 95 02 75 08 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 81 02 05 01 65 11 55 0f 35 00 46 b0 01 26 80 07 75 10 09 30 81 02 46 f3 00 26 38 04 09 31 81 02 05 0d 09 48 09 49 26 ff 00 95 02 75 08 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 81 02 05 01 65 11 55 0f 35 00 46 b0 01 26 80 07 75 10 09 30 81 02 46 f3 00 26 38 04 09 31 81 02 05 0d 09 48 09 49 26 ff 00 95 02 75 08 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 81 02 05 01 65 11 55 0f 35 00 46 b0 01 26 80 07 75 10 09 30 81 02 46 f3 00 26 38 04 09 31 81 02 05 0d 09 48 09 49 26 ff 00 95 02 75 08 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 95 01 81 02 05 01 65 11 55 0f 35 00 46 b0 01 26 80 07 75 10 09 30 81 02 46 f3 00 26 38 04 09 31 81 02 05 0d 09 48 09 49 26 ff 00 95 02 75 08 81 02 c0 05 0d 09 56 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 81 02 09 54 95 01 75 08 15 00 25 0a 81 02 85 84 09 55 b1 02 85 87 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 09 0e a1 01 85 83 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 80 05 09 19 01 29 01 15 00 25 01 95 01 75 01 81 02 95 01 75 07 81 01 05 01 65 11 55 0f 09 30 26 80 07 35 00 46 66 00 75 10 95 01 81 02 09 31 26 38 04 35 00 46 4d 00 81 02 c0 c0",
+ )
+
+
+class Testsipodev_0603_0002(BaseTest.TestPTP):
+ def create_device(self):
+ return PTP(
+ "uhid test sipodev_0603_0002",
+ type="clickpad",
+ rdesc="05 01 09 02 a1 01 85 03 09 01 a1 00 05 09 19 01 29 02 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 80 25 7f 75 08 95 02 81 06 c0 c0 05 0d 09 05 a1 01 85 04 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 75 01 95 02 81 03 95 01 75 04 25 05 09 51 81 02 05 01 15 00 26 44 0a 75 0c 55 0e 65 11 09 30 35 00 46 ac 03 95 01 81 02 46 fe 01 26 34 05 75 0c 09 31 81 02 05 0d c0 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 25 0a 95 01 75 04 81 02 75 01 95 03 81 03 05 09 09 01 25 01 75 01 95 01 81 02 05 0d 85 0a 09 55 09 59 75 04 95 02 25 0f b1 02 85 0b 09 60 75 01 95 01 15 00 25 01 b1 02 95 07 b1 03 85 09 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 0e a1 01 85 06 09 22 a1 02 09 52 15 00 25 0a 75 08 95 01 b1 02 c0 09 22 a1 00 85 07 09 57 09 58 75 01 95 02 25 01 b1 02 95 06 b1 03 c0 c0 05 01 09 0c a1 01 85 08 15 00 25 01 09 c6 75 01 95 01 81 06 75 07 81 03 c0 05 01 09 80 a1 01 85 01 15 00 25 01 75 01 0a 81 00 0a 82 00 0a 83 00 95 03 81 06 95 05 81 01 c0 06 0c 00 09 01 a1 01 85 02 25 01 15 00 75 01 0a b5 00 0a b6 00 0a b7 00 0a cd 00 0a e2 00 0a a2 00 0a e9 00 0a ea 00 95 08 81 02 0a 83 01 0a 6f 00 0a 70 00 0a 88 01 0a 8a 01 0a 92 01 0a a8 02 0a 24 02 95 08 81 02 0a 21 02 0a 23 02 0a 96 01 0a 25 02 0a 26 02 0a 27 02 0a 23 02 0a b1 02 95 08 81 02 c0 06 00 ff 09 01 a1 01 85 05 15 00 26 ff 00 19 01 29 02 75 08 95 05 b1 02 c0",
+ )
+
+
+class Testsynaptics_06cb_1d10(BaseTest.TestWin8Multitouch):
+ def create_device(self):
+ return Digitizer(
+ "uhid test synaptics_06cb_1d10",
+ rdesc="05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 75 08 95 02 15 81 25 7f 35 81 45 7f 55 0e 65 11 81 06 c0 c0 05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 15 01 26 ff 00 95 01 81 42 05 01 15 00 26 3c 0c 75 10 55 0e 65 11 09 30 35 12 46 2a 0c 81 02 09 31 15 00 26 f1 06 35 12 46 df 06 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 15 01 26 ff 00 95 01 81 42 05 01 15 00 26 3c 0c 75 10 55 0e 65 11 09 30 35 12 46 2a 0c 81 02 09 31 15 00 26 f1 06 35 12 46 df 06 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 15 01 26 ff 00 95 01 81 42 05 01 15 00 26 3c 0c 75 10 55 0e 65 11 09 30 35 12 46 2a 0c 81 02 09 31 15 00 26 f1 06 35 12 46 df 06 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 15 01 26 ff 00 95 01 81 42 05 01 15 00 26 3c 0c 75 10 55 0e 65 11 09 30 35 12 46 2a 0c 81 02 09 31 15 00 26 f1 06 35 12 46 df 06 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 75 08 09 51 15 01 26 ff 00 95 01 81 42 05 01 15 00 26 3c 0c 75 10 55 0e 65 11 09 30 35 12 46 2a 0c 81 02 09 31 15 00 26 f1 06 35 12 46 df 06 81 02 c0 05 0d 05 0d 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 95 01 75 08 15 00 25 0f 81 02 85 08 09 55 b1 03 85 07 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 00 ff 09 01 a1 01 85 09 09 02 15 00 26 ff 00 75 08 95 3f 91 02 85 0a 09 03 15 00 26 ff 00 75 08 95 05 91 02 85 0b 09 04 15 00 26 ff 00 75 08 95 3d 81 02 85 0c 09 05 15 00 26 ff 00 75 08 95 01 81 02 85 0f 09 06 15 00 26 ff 00 75 08 95 01 b1 02 c0",
+ )
+
+
+class Testsynaptics_06cb_ce08(BaseTest.TestPTP):
+ def create_device(self):
+ return PTP(
+ "uhid test synaptics_06cb_ce08",
+ max_contacts=5,
+ physical="Vendor Usage 1",
+ input_info=(BusType.I2C, 0x06CB, 0xCE08),
+ rdesc="05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 01 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0 05 01 09 02 a1 01 85 18 09 01 a1 00 05 09 19 01 29 03 46 00 00 15 00 25 01 75 01 95 03 81 02 95 05 81 01 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0 06 00 ff 09 02 a1 01 85 20 09 01 a1 00 09 03 15 00 26 ff 00 35 00 46 ff 00 75 08 95 05 81 02 c0 c0 05 0d 09 05 a1 01 85 03 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 f8 04 75 10 55 0e 65 11 09 30 35 00 46 24 04 95 01 81 02 46 30 02 26 a0 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 f8 04 75 10 55 0e 65 11 09 30 35 00 46 24 04 95 01 81 02 46 30 02 26 a0 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 f8 04 75 10 55 0e 65 11 09 30 35 00 46 24 04 95 01 81 02 46 30 02 26 a0 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 f8 04 75 10 55 0e 65 11 09 30 35 00 46 24 04 95 01 81 02 46 30 02 26 a0 02 09 31 81 02 c0 05 0d 09 22 a1 02 15 00 25 01 09 47 09 42 95 02 75 01 81 02 95 01 75 03 25 05 09 51 81 02 75 01 95 03 81 03 05 01 15 00 26 f8 04 75 10 55 0e 65 11 09 30 35 00 46 24 04 95 01 81 02 46 30 02 26 a0 02 09 31 81 02 c0 05 0d 55 0c 66 01 10 47 ff ff 00 00 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 25 7f 95 01 75 08 81 02 05 09 09 01 25 01 75 01 95 01 81 02 95 07 81 03 05 0d 85 08 09 55 09 59 75 04 95 02 25 0f b1 02 85 0d 09 60 75 01 95 01 15 00 25 01 b1 02 95 07 b1 03 85 07 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 0e a1 01 85 04 09 22 a1 02 09 52 15 00 25 0a 75 08 95 01 b1 02 c0 09 22 a1 00 85 06 09 57 09 58 75 01 95 02 25 01 b1 02 95 06 b1 03 c0 c0 06 00 ff 09 01 a1 01 85 09 09 02 15 00 26 ff 00 75 08 95 14 91 02 85 0a 09 03 15 00 26 ff 00 75 08 95 14 91 02 85 0b 09 04 15 00 26 ff 00 75 08 95 45 81 02 85 0c 09 05 15 00 26 ff 00 75 08 95 45 81 02 85 0f 09 06 15 00 26 ff 00 75 08 95 03 b1 02 85 0e 09 07 15 00 26 ff 00 75 08 95 01 b1 02 c0",
+ )
diff --git a/tools/testing/selftests/hid/tests/test_sony.py b/tools/testing/selftests/hid/tests/test_sony.py
new file mode 100644
index 000000000000..7e52c28e59c5
--- /dev/null
+++ b/tools/testing/selftests/hid/tests/test_sony.py
@@ -0,0 +1,342 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2020 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2020 Red Hat, Inc.
+#
+
+from .base import application_matches
+from .test_gamepad import BaseTest
+from hidtools.device.sony_gamepad import (
+ PS3Controller,
+ PS4ControllerBluetooth,
+ PS4ControllerUSB,
+ PS5ControllerBluetooth,
+ PS5ControllerUSB,
+ PSTouchPoint,
+)
+from hidtools.util import BusType
+
+import libevdev
+import logging
+import pytest
+
+logger = logging.getLogger("hidtools.test.sony")
+
+PS3_MODULE = ("sony", "hid_sony")
+PS4_MODULE = ("playstation", "hid_playstation")
+PS5_MODULE = ("playstation", "hid_playstation")
+
+
+class SonyBaseTest:
+ class SonyTest(BaseTest.TestGamepad):
+ pass
+
+ class SonyPS4ControllerTest(SonyTest):
+ kernel_modules = [PS4_MODULE]
+
+ def test_accelerometer(self):
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev("Accelerometer")
+
+ for x in range(-32000, 32000, 4000):
+ r = uhdev.event(accel=(x, None, None))
+ events = uhdev.next_sync_events("Accelerometer")
+ self.debug_reports(r, uhdev, events)
+
+ assert libevdev.InputEvent(libevdev.EV_ABS.ABS_X) in events
+ value = evdev.value[libevdev.EV_ABS.ABS_X]
+ # Check against range due to small loss in precision due
+ # to inverse calibration, followed by calibration by hid-sony.
+ assert x - 1 <= value <= x + 1
+
+ for y in range(-32000, 32000, 4000):
+ r = uhdev.event(accel=(None, y, None))
+ events = uhdev.next_sync_events("Accelerometer")
+ self.debug_reports(r, uhdev, events)
+
+ assert libevdev.InputEvent(libevdev.EV_ABS.ABS_Y) in events
+ value = evdev.value[libevdev.EV_ABS.ABS_Y]
+ assert y - 1 <= value <= y + 1
+
+ for z in range(-32000, 32000, 4000):
+ r = uhdev.event(accel=(None, None, z))
+ events = uhdev.next_sync_events("Accelerometer")
+ self.debug_reports(r, uhdev, events)
+
+ assert libevdev.InputEvent(libevdev.EV_ABS.ABS_Z) in events
+ value = evdev.value[libevdev.EV_ABS.ABS_Z]
+ assert z - 1 <= value <= z + 1
+
+ def test_gyroscope(self):
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev("Accelerometer")
+
+ for rx in range(-2000000, 2000000, 200000):
+ r = uhdev.event(gyro=(rx, None, None))
+ events = uhdev.next_sync_events("Accelerometer")
+ self.debug_reports(r, uhdev, events)
+
+ assert libevdev.InputEvent(libevdev.EV_ABS.ABS_RX) in events
+ value = evdev.value[libevdev.EV_ABS.ABS_RX]
+ # Sensor internal value is 16-bit, but calibrated is 22-bit, so
+ # 6-bit (64) difference, so allow a range of +/- 64.
+ assert rx - 64 <= value <= rx + 64
+
+ for ry in range(-2000000, 2000000, 200000):
+ r = uhdev.event(gyro=(None, ry, None))
+ events = uhdev.next_sync_events("Accelerometer")
+ self.debug_reports(r, uhdev, events)
+
+ assert libevdev.InputEvent(libevdev.EV_ABS.ABS_RY) in events
+ value = evdev.value[libevdev.EV_ABS.ABS_RY]
+ assert ry - 64 <= value <= ry + 64
+
+ for rz in range(-2000000, 2000000, 200000):
+ r = uhdev.event(gyro=(None, None, rz))
+ events = uhdev.next_sync_events("Accelerometer")
+ self.debug_reports(r, uhdev, events)
+
+ assert libevdev.InputEvent(libevdev.EV_ABS.ABS_RZ) in events
+ value = evdev.value[libevdev.EV_ABS.ABS_RZ]
+ assert rz - 64 <= value <= rz + 64
+
+ def test_battery(self):
+ uhdev = self.uhdev
+
+ assert uhdev.power_supply_class is not None
+
+ # DS4 capacity levels are in increments of 10.
+ # Battery is never below 5%.
+ for i in range(5, 105, 10):
+ uhdev.battery.capacity = i
+ uhdev.event()
+ assert uhdev.power_supply_class.capacity == i
+
+ # Discharging tests only make sense for BlueTooth.
+ if uhdev.bus == BusType.BLUETOOTH:
+ uhdev.battery.cable_connected = False
+ uhdev.battery.capacity = 45
+ uhdev.event()
+ assert uhdev.power_supply_class.status == "Discharging"
+
+ uhdev.battery.cable_connected = True
+ uhdev.battery.capacity = 5
+ uhdev.event()
+ assert uhdev.power_supply_class.status == "Charging"
+
+ uhdev.battery.capacity = 100
+ uhdev.event()
+ assert uhdev.power_supply_class.status == "Charging"
+
+ uhdev.battery.full = True
+ uhdev.event()
+ assert uhdev.power_supply_class.status == "Full"
+
+ def test_mt_single_touch(self):
+ """send a single touch in the first slot of the device,
+ and release it."""
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev("Touch Pad")
+
+ t0 = PSTouchPoint(1, 50, 100)
+ r = uhdev.event(touch=[t0])
+ events = uhdev.next_sync_events("Touch Pad")
+ self.debug_reports(r, uhdev, events)
+
+ assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
+
+ t0.tipswitch = False
+ r = uhdev.event(touch=[t0])
+ events = uhdev.next_sync_events("Touch Pad")
+ self.debug_reports(r, uhdev, events)
+ assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+ def test_mt_dual_touch(self):
+ """Send 2 touches in the first 2 slots.
+ Make sure the kernel sees this as a dual touch.
+ Release and check
+
+ Note: PTP will send here BTN_DOUBLETAP emulation"""
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev("Touch Pad")
+
+ t0 = PSTouchPoint(1, 50, 100)
+ t1 = PSTouchPoint(2, 150, 200)
+
+ r = uhdev.event(touch=[t0])
+ events = uhdev.next_sync_events("Touch Pad")
+ self.debug_reports(r, uhdev, events)
+
+ assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
+ assert evdev.value[libevdev.EV_KEY.BTN_TOUCH] == 1
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
+ assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+ r = uhdev.event(touch=[t0, t1])
+ events = uhdev.next_sync_events("Touch Pad")
+ self.debug_reports(r, uhdev, events)
+ assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH) not in events
+ assert evdev.value[libevdev.EV_KEY.BTN_TOUCH] == 1
+ assert (
+ libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_X, 5) not in events
+ )
+ assert (
+ libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_Y, 10) not in events
+ )
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
+ assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1
+ assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_X] == 150
+ assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 200
+
+ t0.tipswitch = False
+ r = uhdev.event(touch=[t0, t1])
+ events = uhdev.next_sync_events("Touch Pad")
+ self.debug_reports(r, uhdev, events)
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+ assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1
+ assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_X) not in events
+ assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_Y) not in events
+
+ t1.tipswitch = False
+ r = uhdev.event(touch=[t1])
+
+ events = uhdev.next_sync_events("Touch Pad")
+ self.debug_reports(r, uhdev, events)
+ assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+ assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+
+class TestPS3Controller(SonyBaseTest.SonyTest):
+ kernel_modules = [PS3_MODULE]
+
+ def create_device(self):
+ controller = PS3Controller()
+ controller.application_matches = application_matches
+ return controller
+
+ @pytest.fixture(autouse=True)
+ def start_controller(self):
+ # emulate a 'PS' button press to tell the kernel we are ready to accept events
+ self.assert_button(17)
+
+ # drain any remaining udev events
+ while self.uhdev.dispatch(10):
+ pass
+
+ def test_led(self):
+ for k, v in self.uhdev.led_classes.items():
+ # the kernel might have set a LED for us
+ logger.info(f"{k}: {v.brightness}")
+
+ idx = int(k[-1]) - 1
+ assert self.uhdev.hw_leds.get_led(idx)[0] == bool(v.brightness)
+
+ v.brightness = 0
+ self.uhdev.dispatch(10)
+ assert self.uhdev.hw_leds.get_led(idx)[0] is False
+
+ v.brightness = v.max_brightness
+ self.uhdev.dispatch(10)
+ assert self.uhdev.hw_leds.get_led(idx)[0]
+
+
+class CalibratedPS4Controller(object):
+ # DS4 reports uncalibrated sensor data. Calibration coefficients
+ # can be retrieved using a feature report (0x2 USB / 0x5 BT).
+ # The values below are the processed calibration values for the
+ # DS4s matching the feature reports of PS4ControllerBluetooth/USB
+ # as dumped from hid-sony 'ds4_get_calibration_data'.
+ #
+ # Note we duplicate those values here in case the kernel changes them
+ # so we can have tests passing even if hid-tools doesn't have the
+ # correct values.
+ accelerometer_calibration_data = {
+ "x": {"bias": -73, "numer": 16384, "denom": 16472},
+ "y": {"bias": -352, "numer": 16384, "denom": 16344},
+ "z": {"bias": 81, "numer": 16384, "denom": 16319},
+ }
+ gyroscope_calibration_data = {
+ "x": {"bias": 0, "numer": 1105920, "denom": 17827},
+ "y": {"bias": 0, "numer": 1105920, "denom": 17777},
+ "z": {"bias": 0, "numer": 1105920, "denom": 17748},
+ }
+
+
+class CalibratedPS4ControllerBluetooth(CalibratedPS4Controller, PS4ControllerBluetooth):
+ pass
+
+
+class TestPS4ControllerBluetooth(SonyBaseTest.SonyPS4ControllerTest):
+ def create_device(self):
+ controller = CalibratedPS4ControllerBluetooth()
+ controller.application_matches = application_matches
+ return controller
+
+
+class CalibratedPS4ControllerUSB(CalibratedPS4Controller, PS4ControllerUSB):
+ pass
+
+
+class TestPS4ControllerUSB(SonyBaseTest.SonyPS4ControllerTest):
+ def create_device(self):
+ controller = CalibratedPS4ControllerUSB()
+ controller.application_matches = application_matches
+ return controller
+
+
+class CalibratedPS5Controller(object):
+ # DualSense reports uncalibrated sensor data. Calibration coefficients
+ # can be retrieved using feature report 0x09.
+ # The values below are the processed calibration values for the
+ # DualSene matching the feature reports of PS5ControllerBluetooth/USB
+ # as dumped from hid-playstation 'dualsense_get_calibration_data'.
+ #
+ # Note we duplicate those values here in case the kernel changes them
+ # so we can have tests passing even if hid-tools doesn't have the
+ # correct values.
+ accelerometer_calibration_data = {
+ "x": {"bias": 0, "numer": 16384, "denom": 16374},
+ "y": {"bias": -114, "numer": 16384, "denom": 16362},
+ "z": {"bias": 2, "numer": 16384, "denom": 16395},
+ }
+ gyroscope_calibration_data = {
+ "x": {"bias": 0, "numer": 1105920, "denom": 17727},
+ "y": {"bias": 0, "numer": 1105920, "denom": 17728},
+ "z": {"bias": 0, "numer": 1105920, "denom": 17769},
+ }
+
+
+class CalibratedPS5ControllerBluetooth(CalibratedPS5Controller, PS5ControllerBluetooth):
+ pass
+
+
+class TestPS5ControllerBluetooth(SonyBaseTest.SonyPS4ControllerTest):
+ kernel_modules = [PS5_MODULE]
+
+ def create_device(self):
+ controller = CalibratedPS5ControllerBluetooth()
+ controller.application_matches = application_matches
+ return controller
+
+
+class CalibratedPS5ControllerUSB(CalibratedPS5Controller, PS5ControllerUSB):
+ pass
+
+
+class TestPS5ControllerUSB(SonyBaseTest.SonyPS4ControllerTest):
+ kernel_modules = [PS5_MODULE]
+
+ def create_device(self):
+ controller = CalibratedPS5ControllerUSB()
+ controller.application_matches = application_matches
+ return controller
diff --git a/tools/testing/selftests/hid/tests/test_tablet.py b/tools/testing/selftests/hid/tests/test_tablet.py
new file mode 100644
index 000000000000..a9e2de1e8861
--- /dev/null
+++ b/tools/testing/selftests/hid/tests/test_tablet.py
@@ -0,0 +1,1505 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2021 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2021 Red Hat, Inc.
+#
+
+from . import base
+import copy
+from enum import Enum
+from hidtools.util import BusType
+import libevdev
+import logging
+import pytest
+from typing import Dict, List, Optional, Tuple
+
+logger = logging.getLogger("hidtools.test.tablet")
+
+
+class BtnTouch(Enum):
+ """Represents whether the BTN_TOUCH event is set to True or False"""
+
+ DOWN = True
+ UP = False
+
+
+class ToolType(Enum):
+ PEN = libevdev.EV_KEY.BTN_TOOL_PEN
+ RUBBER = libevdev.EV_KEY.BTN_TOOL_RUBBER
+
+
+class BtnPressed(Enum):
+ """Represents whether a button is pressed on the stylus"""
+
+ PRIMARY_PRESSED = libevdev.EV_KEY.BTN_STYLUS
+ SECONDARY_PRESSED = libevdev.EV_KEY.BTN_STYLUS2
+ THIRD_PRESSED = libevdev.EV_KEY.BTN_STYLUS3
+
+
+class PenState(Enum):
+ """Pen states according to Microsoft reference:
+ https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
+
+ We extend it with the various buttons when we need to check them.
+ """
+
+ PEN_IS_OUT_OF_RANGE = BtnTouch.UP, None, False
+ PEN_IS_IN_RANGE = BtnTouch.UP, ToolType.PEN, False
+ PEN_IS_IN_RANGE_WITH_BUTTON = BtnTouch.UP, ToolType.PEN, True
+ PEN_IS_IN_CONTACT = BtnTouch.DOWN, ToolType.PEN, False
+ PEN_IS_IN_CONTACT_WITH_BUTTON = BtnTouch.DOWN, ToolType.PEN, True
+ PEN_IS_IN_RANGE_WITH_ERASING_INTENT = BtnTouch.UP, ToolType.RUBBER, False
+ PEN_IS_IN_RANGE_WITH_ERASING_INTENT_WITH_BUTTON = BtnTouch.UP, ToolType.RUBBER, True
+ PEN_IS_ERASING = BtnTouch.DOWN, ToolType.RUBBER, False
+ PEN_IS_ERASING_WITH_BUTTON = BtnTouch.DOWN, ToolType.RUBBER, True
+
+ def __init__(
+ self, touch: BtnTouch, tool: Optional[ToolType], button: Optional[bool]
+ ):
+ self.touch = touch # type: ignore
+ self.tool = tool # type: ignore
+ self.button = button # type: ignore
+
+ @classmethod
+ def from_evdev(cls, evdev, test_button) -> "PenState":
+ touch = BtnTouch(evdev.value[libevdev.EV_KEY.BTN_TOUCH])
+ tool = None
+ button = False
+ if (
+ evdev.value[libevdev.EV_KEY.BTN_TOOL_RUBBER]
+ and not evdev.value[libevdev.EV_KEY.BTN_TOOL_PEN]
+ ):
+ tool = ToolType(libevdev.EV_KEY.BTN_TOOL_RUBBER)
+ elif (
+ evdev.value[libevdev.EV_KEY.BTN_TOOL_PEN]
+ and not evdev.value[libevdev.EV_KEY.BTN_TOOL_RUBBER]
+ ):
+ tool = ToolType(libevdev.EV_KEY.BTN_TOOL_PEN)
+ elif (
+ evdev.value[libevdev.EV_KEY.BTN_TOOL_PEN]
+ or evdev.value[libevdev.EV_KEY.BTN_TOOL_RUBBER]
+ ):
+ raise ValueError("2 tools are not allowed")
+
+ # we take only the provided button into account
+ if test_button is not None:
+ button = bool(evdev.value[test_button.value])
+
+ # the kernel tends to insert an EV_SYN once removing the tool, so
+ # the button will be released after
+ if tool is None:
+ button = False
+
+ return cls((touch, tool, button)) # type: ignore
+
+ def apply(
+ self, events: List[libevdev.InputEvent], strict: bool, test_button: BtnPressed
+ ) -> "PenState":
+ if libevdev.EV_SYN.SYN_REPORT in events:
+ raise ValueError("EV_SYN is in the event sequence")
+ touch = self.touch
+ touch_found = False
+ tool = self.tool
+ tool_found = False
+ button = self.button
+ button_found = False
+
+ for ev in events:
+ if ev == libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH):
+ if touch_found:
+ raise ValueError(f"duplicated BTN_TOUCH in {events}")
+ touch_found = True
+ touch = BtnTouch(ev.value)
+ elif ev in (
+ libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN),
+ libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_RUBBER),
+ ):
+ if tool_found:
+ raise ValueError(f"duplicated BTN_TOOL_* in {events}")
+ tool_found = True
+ tool = ToolType(ev.code) if ev.value else None
+ elif test_button is not None and ev in (test_button.value,):
+ if button_found:
+ raise ValueError(f"duplicated BTN_STYLUS* in {events}")
+ button_found = True
+ button = bool(ev.value)
+
+ # the kernel tends to insert an EV_SYN once removing the tool, so
+ # the button will be released after
+ if tool is None:
+ button = False
+
+ new_state = PenState((touch, tool, button)) # type: ignore
+ if strict:
+ assert (
+ new_state in self.valid_transitions()
+ ), f"moving from {self} to {new_state} is forbidden"
+ else:
+ assert (
+ new_state in self.historically_tolerated_transitions()
+ ), f"moving from {self} to {new_state} is forbidden"
+
+ return new_state
+
+ def valid_transitions(self) -> Tuple["PenState", ...]:
+ """Following the state machine in the URL above.
+
+ Note that those transitions are from the evdev point of view, not HID"""
+ if self == PenState.PEN_IS_OUT_OF_RANGE:
+ return (
+ PenState.PEN_IS_OUT_OF_RANGE,
+ PenState.PEN_IS_IN_RANGE,
+ PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
+ PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+ PenState.PEN_IS_IN_CONTACT,
+ PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
+ PenState.PEN_IS_ERASING,
+ )
+
+ if self == PenState.PEN_IS_IN_RANGE:
+ return (
+ PenState.PEN_IS_IN_RANGE,
+ PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
+ PenState.PEN_IS_OUT_OF_RANGE,
+ PenState.PEN_IS_IN_CONTACT,
+ )
+
+ if self == PenState.PEN_IS_IN_CONTACT:
+ return (
+ PenState.PEN_IS_IN_CONTACT,
+ PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
+ PenState.PEN_IS_IN_RANGE,
+ )
+
+ if self == PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT:
+ return (
+ PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+ PenState.PEN_IS_OUT_OF_RANGE,
+ PenState.PEN_IS_ERASING,
+ )
+
+ if self == PenState.PEN_IS_ERASING:
+ return (
+ PenState.PEN_IS_ERASING,
+ PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+ )
+
+ if self == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
+ return (
+ PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
+ PenState.PEN_IS_IN_RANGE,
+ PenState.PEN_IS_OUT_OF_RANGE,
+ PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
+ )
+
+ if self == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
+ return (
+ PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
+ PenState.PEN_IS_IN_CONTACT,
+ PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
+ )
+
+ return tuple()
+
+ def historically_tolerated_transitions(self) -> Tuple["PenState", ...]:
+ """Following the state machine in the URL above, with a couple of addition
+ for skipping the in-range state, due to historical reasons.
+
+ Note that those transitions are from the evdev point of view, not HID"""
+ if self == PenState.PEN_IS_OUT_OF_RANGE:
+ return (
+ PenState.PEN_IS_OUT_OF_RANGE,
+ PenState.PEN_IS_IN_RANGE,
+ PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
+ PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+ PenState.PEN_IS_IN_CONTACT,
+ PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
+ PenState.PEN_IS_ERASING,
+ )
+
+ if self == PenState.PEN_IS_IN_RANGE:
+ return (
+ PenState.PEN_IS_IN_RANGE,
+ PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
+ PenState.PEN_IS_OUT_OF_RANGE,
+ PenState.PEN_IS_IN_CONTACT,
+ )
+
+ if self == PenState.PEN_IS_IN_CONTACT:
+ return (
+ PenState.PEN_IS_IN_CONTACT,
+ PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
+ PenState.PEN_IS_IN_RANGE,
+ PenState.PEN_IS_OUT_OF_RANGE,
+ )
+
+ if self == PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT:
+ return (
+ PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+ PenState.PEN_IS_OUT_OF_RANGE,
+ PenState.PEN_IS_ERASING,
+ )
+
+ if self == PenState.PEN_IS_ERASING:
+ return (
+ PenState.PEN_IS_ERASING,
+ PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+ PenState.PEN_IS_OUT_OF_RANGE,
+ )
+
+ if self == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
+ return (
+ PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
+ PenState.PEN_IS_IN_RANGE,
+ PenState.PEN_IS_OUT_OF_RANGE,
+ PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
+ )
+
+ if self == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
+ return (
+ PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
+ PenState.PEN_IS_IN_CONTACT,
+ PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
+ PenState.PEN_IS_OUT_OF_RANGE,
+ )
+
+ return tuple()
+
+ @staticmethod
+ def legal_transitions() -> Dict[str, Tuple["PenState", ...]]:
+ """This is the first half of the Windows Pen Implementation state machine:
+ we don't have Invert nor Erase bits, so just move in/out-of-range or proximity.
+ https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
+ """
+ return {
+ "in-range": (PenState.PEN_IS_IN_RANGE,),
+ "in-range -> out-of-range": (
+ PenState.PEN_IS_IN_RANGE,
+ PenState.PEN_IS_OUT_OF_RANGE,
+ ),
+ "in-range -> touch": (PenState.PEN_IS_IN_RANGE, PenState.PEN_IS_IN_CONTACT),
+ "in-range -> touch -> release": (
+ PenState.PEN_IS_IN_RANGE,
+ PenState.PEN_IS_IN_CONTACT,
+ PenState.PEN_IS_IN_RANGE,
+ ),
+ "in-range -> touch -> release -> out-of-range": (
+ PenState.PEN_IS_IN_RANGE,
+ PenState.PEN_IS_IN_CONTACT,
+ PenState.PEN_IS_IN_RANGE,
+ PenState.PEN_IS_OUT_OF_RANGE,
+ ),
+ }
+
+ @staticmethod
+ def legal_transitions_with_invert() -> Dict[str, Tuple["PenState", ...]]:
+ """This is the second half of the Windows Pen Implementation state machine:
+ we now have Invert and Erase bits, so move in/out or proximity with the intend
+ to erase.
+ https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
+ """
+ return {
+ "hover-erasing": (PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,),
+ "hover-erasing -> out-of-range": (
+ PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+ PenState.PEN_IS_OUT_OF_RANGE,
+ ),
+ "hover-erasing -> erase": (
+ PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+ PenState.PEN_IS_ERASING,
+ ),
+ "hover-erasing -> erase -> release": (
+ PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+ PenState.PEN_IS_ERASING,
+ PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+ ),
+ "hover-erasing -> erase -> release -> out-of-range": (
+ PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+ PenState.PEN_IS_ERASING,
+ PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+ PenState.PEN_IS_OUT_OF_RANGE,
+ ),
+ "hover-erasing -> in-range": (
+ PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+ PenState.PEN_IS_IN_RANGE,
+ ),
+ "in-range -> hover-erasing": (
+ PenState.PEN_IS_IN_RANGE,
+ PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+ ),
+ }
+
+ @staticmethod
+ def legal_transitions_with_button() -> Dict[str, Tuple["PenState", ...]]:
+ """We revisit the Windows Pen Implementation state machine:
+ we now have a button.
+ """
+ return {
+ "hover-button": (PenState.PEN_IS_IN_RANGE_WITH_BUTTON,),
+ "hover-button -> out-of-range": (
+ PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
+ PenState.PEN_IS_OUT_OF_RANGE,
+ ),
+ "in-range -> button-press": (
+ PenState.PEN_IS_IN_RANGE,
+ PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
+ ),
+ "in-range -> button-press -> button-release": (
+ PenState.PEN_IS_IN_RANGE,
+ PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
+ PenState.PEN_IS_IN_RANGE,
+ ),
+ "in-range -> touch -> button-press -> button-release": (
+ PenState.PEN_IS_IN_RANGE,
+ PenState.PEN_IS_IN_CONTACT,
+ PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
+ PenState.PEN_IS_IN_CONTACT,
+ ),
+ "in-range -> touch -> button-press -> release -> button-release": (
+ PenState.PEN_IS_IN_RANGE,
+ PenState.PEN_IS_IN_CONTACT,
+ PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
+ PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
+ PenState.PEN_IS_IN_RANGE,
+ ),
+ "in-range -> button-press -> touch -> release -> button-release": (
+ PenState.PEN_IS_IN_RANGE,
+ PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
+ PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
+ PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
+ PenState.PEN_IS_IN_RANGE,
+ ),
+ "in-range -> button-press -> touch -> button-release -> release": (
+ PenState.PEN_IS_IN_RANGE,
+ PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
+ PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
+ PenState.PEN_IS_IN_CONTACT,
+ PenState.PEN_IS_IN_RANGE,
+ ),
+ }
+
+ @staticmethod
+ def tolerated_transitions() -> Dict[str, Tuple["PenState", ...]]:
+ """This is not adhering to the Windows Pen Implementation state machine
+ but we should expect the kernel to behave properly, mostly for historical
+ reasons."""
+ return {
+ "direct-in-contact": (PenState.PEN_IS_IN_CONTACT,),
+ "direct-in-contact -> out-of-range": (
+ PenState.PEN_IS_IN_CONTACT,
+ PenState.PEN_IS_OUT_OF_RANGE,
+ ),
+ }
+
+ @staticmethod
+ def tolerated_transitions_with_invert() -> Dict[str, Tuple["PenState", ...]]:
+ """This is the second half of the Windows Pen Implementation state machine:
+ we now have Invert and Erase bits, so move in/out or proximity with the intend
+ to erase.
+ https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
+ """
+ return {
+ "direct-erase": (PenState.PEN_IS_ERASING,),
+ "direct-erase -> out-of-range": (
+ PenState.PEN_IS_ERASING,
+ PenState.PEN_IS_OUT_OF_RANGE,
+ ),
+ }
+
+ @staticmethod
+ def broken_transitions() -> Dict[str, Tuple["PenState", ...]]:
+ """Those tests are definitely not part of the Windows specification.
+ However, a half broken device might export those transitions.
+ For example, a pen that has the eraser button might wobble between
+ touching and erasing if the tablet doesn't enforce the Windows
+ state machine."""
+ return {
+ "in-range -> touch -> erase -> hover-erase": (
+ PenState.PEN_IS_IN_RANGE,
+ PenState.PEN_IS_IN_CONTACT,
+ PenState.PEN_IS_ERASING,
+ PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+ ),
+ "in-range -> erase -> hover-erase": (
+ PenState.PEN_IS_IN_RANGE,
+ PenState.PEN_IS_ERASING,
+ PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+ ),
+ "hover-erase -> erase -> touch -> in-range": (
+ PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+ PenState.PEN_IS_ERASING,
+ PenState.PEN_IS_IN_CONTACT,
+ PenState.PEN_IS_IN_RANGE,
+ ),
+ "hover-erase -> touch -> in-range": (
+ PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+ PenState.PEN_IS_IN_CONTACT,
+ PenState.PEN_IS_IN_RANGE,
+ ),
+ "touch -> erase -> touch -> erase": (
+ PenState.PEN_IS_IN_CONTACT,
+ PenState.PEN_IS_ERASING,
+ PenState.PEN_IS_IN_CONTACT,
+ PenState.PEN_IS_ERASING,
+ ),
+ }
+
+
+class Pen(object):
+ def __init__(self, x, y):
+ self.x = x
+ self.y = y
+ self.tipswitch = False
+ self.tippressure = 15
+ self.azimuth = 0
+ self.inrange = False
+ self.width = 10
+ self.height = 10
+ self.barrelswitch = False
+ self.secondarybarrelswitch = False
+ self.invert = False
+ self.eraser = False
+ self.xtilt = 1
+ self.ytilt = 1
+ self.twist = 1
+ self._old_values = None
+ self.current_state = None
+
+ def restore(self):
+ if self._old_values is not None:
+ for i in [
+ "x",
+ "y",
+ "tippressure",
+ "azimuth",
+ "width",
+ "height",
+ "twist",
+ "xtilt",
+ "ytilt",
+ ]:
+ setattr(self, i, getattr(self._old_values, i))
+
+ def backup(self):
+ self._old_values = copy.copy(self)
+
+ def __assert_axis(self, evdev, axis, value):
+ if (
+ axis == libevdev.EV_KEY.BTN_TOOL_RUBBER
+ and evdev.value[libevdev.EV_KEY.BTN_TOOL_RUBBER] is None
+ ):
+ return
+
+ assert (
+ evdev.value[axis] == value
+ ), f"assert evdev.value[{axis}] ({evdev.value[axis]}) != {value}"
+
+ def assert_expected_input_events(self, evdev, button):
+ assert evdev.value[libevdev.EV_ABS.ABS_X] == self.x
+ assert evdev.value[libevdev.EV_ABS.ABS_Y] == self.y
+
+ # assert no other buttons than the tested ones are set
+ buttons = [
+ BtnPressed.PRIMARY_PRESSED,
+ BtnPressed.SECONDARY_PRESSED,
+ BtnPressed.THIRD_PRESSED,
+ ]
+ if button is not None:
+ buttons.remove(button)
+ for b in buttons:
+ assert evdev.value[b.value] is None or evdev.value[b.value] == False
+
+ assert self.current_state == PenState.from_evdev(evdev, button)
+
+
+class PenDigitizer(base.UHIDTestDevice):
+ def __init__(
+ self,
+ name,
+ rdesc_str=None,
+ rdesc=None,
+ application="Pen",
+ physical="Stylus",
+ input_info=(BusType.USB, 1, 2),
+ evdev_name_suffix=None,
+ ):
+ super().__init__(name, application, rdesc_str, rdesc, input_info)
+ self.physical = physical
+ self.cur_application = application
+ if evdev_name_suffix is not None:
+ self.name += evdev_name_suffix
+
+ self.fields = []
+ for r in self.parsed_rdesc.input_reports.values():
+ if r.application_name == self.application:
+ physicals = [f.physical_name for f in r]
+ if self.physical not in physicals and None not in physicals:
+ continue
+ self.fields = [f.usage_name for f in r]
+
+ def move_to(self, pen, state, button):
+ # fill in the previous values
+ if pen.current_state == PenState.PEN_IS_OUT_OF_RANGE:
+ pen.restore()
+
+ print(f"\n *** pen is moving to {state} ***")
+
+ if state == PenState.PEN_IS_OUT_OF_RANGE:
+ pen.backup()
+ pen.x = 0
+ pen.y = 0
+ pen.tipswitch = False
+ pen.tippressure = 0
+ pen.azimuth = 0
+ pen.inrange = False
+ pen.width = 0
+ pen.height = 0
+ pen.invert = False
+ pen.eraser = False
+ pen.xtilt = 0
+ pen.ytilt = 0
+ pen.twist = 0
+ pen.barrelswitch = False
+ pen.secondarybarrelswitch = False
+ elif state == PenState.PEN_IS_IN_RANGE:
+ pen.tipswitch = False
+ pen.inrange = True
+ pen.invert = False
+ pen.eraser = False
+ pen.barrelswitch = False
+ pen.secondarybarrelswitch = False
+ elif state == PenState.PEN_IS_IN_CONTACT:
+ pen.tipswitch = True
+ pen.inrange = True
+ pen.invert = False
+ pen.eraser = False
+ pen.barrelswitch = False
+ pen.secondarybarrelswitch = False
+ elif state == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
+ pen.tipswitch = False
+ pen.inrange = True
+ pen.invert = False
+ pen.eraser = False
+ assert button is not None
+ pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
+ pen.secondarybarrelswitch = button == BtnPressed.SECONDARY_PRESSED
+ elif state == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
+ pen.tipswitch = True
+ pen.inrange = True
+ pen.invert = False
+ pen.eraser = False
+ assert button is not None
+ pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
+ pen.secondarybarrelswitch = button == BtnPressed.SECONDARY_PRESSED
+ elif state == PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT:
+ pen.tipswitch = False
+ pen.inrange = True
+ pen.invert = True
+ pen.eraser = False
+ pen.barrelswitch = False
+ pen.secondarybarrelswitch = False
+ elif state == PenState.PEN_IS_ERASING:
+ pen.tipswitch = False
+ pen.inrange = True
+ pen.invert = False
+ pen.eraser = True
+ pen.barrelswitch = False
+ pen.secondarybarrelswitch = False
+
+ pen.current_state = state
+
+ def event(self, pen, button):
+ rs = []
+ r = self.create_report(application=self.cur_application, data=pen)
+ self.call_input_event(r)
+ rs.append(r)
+ return rs
+
+ def get_report(self, req, rnum, rtype):
+ if rtype != self.UHID_FEATURE_REPORT:
+ return (1, [])
+
+ rdesc = None
+ for v in self.parsed_rdesc.feature_reports.values():
+ if v.report_ID == rnum:
+ rdesc = v
+
+ if rdesc is None:
+ return (1, [])
+
+ return (1, [])
+
+ def set_report(self, req, rnum, rtype, data):
+ if rtype != self.UHID_FEATURE_REPORT:
+ return 1
+
+ rdesc = None
+ for v in self.parsed_rdesc.feature_reports.values():
+ if v.report_ID == rnum:
+ rdesc = v
+
+ if rdesc is None:
+ return 1
+
+ return 1
+
+
+class BaseTest:
+ class TestTablet(base.BaseTestCase.TestUhid):
+ def create_device(self):
+ raise Exception("please reimplement me in subclasses")
+
+ def post(self, uhdev, pen, test_button):
+ r = uhdev.event(pen, test_button)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ return events
+
+ def validate_transitions(
+ self, from_state, pen, evdev, events, allow_intermediate_states, button
+ ):
+ # check that the final state is correct
+ pen.assert_expected_input_events(evdev, button)
+
+ state = from_state
+
+ # check that the transitions are valid
+ sync_events = []
+ while libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT) in events:
+ # split the first EV_SYN from the list
+ idx = events.index(libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT))
+ sync_events = events[:idx]
+ events = events[idx + 1 :]
+
+ # now check for a valid transition
+ state = state.apply(sync_events, not allow_intermediate_states, button)
+
+ if events:
+ state = state.apply(sync_events, not allow_intermediate_states, button)
+
+ def _test_states(
+ self, state_list, scribble, allow_intermediate_states, button=None
+ ):
+ """Internal method to test against a list of
+ transition between states.
+ state_list is a list of PenState objects
+ scribble is a boolean which tells if we need
+ to wobble a little the X,Y coordinates of the pen
+ between each state transition."""
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+
+ cur_state = PenState.PEN_IS_OUT_OF_RANGE
+
+ p = Pen(50, 60)
+ uhdev.move_to(p, PenState.PEN_IS_OUT_OF_RANGE, button)
+ events = self.post(uhdev, p, button)
+ self.validate_transitions(
+ cur_state, p, evdev, events, allow_intermediate_states, button
+ )
+
+ cur_state = p.current_state
+
+ for state in state_list:
+ if scribble and cur_state != PenState.PEN_IS_OUT_OF_RANGE:
+ p.x += 1
+ p.y -= 1
+ events = self.post(uhdev, p, button)
+ self.validate_transitions(
+ cur_state, p, evdev, events, allow_intermediate_states, button
+ )
+ assert len(events) >= 3 # X, Y, SYN
+ uhdev.move_to(p, state, button)
+ if scribble and state != PenState.PEN_IS_OUT_OF_RANGE:
+ p.x += 1
+ p.y -= 1
+ events = self.post(uhdev, p, button)
+ self.validate_transitions(
+ cur_state, p, evdev, events, allow_intermediate_states, button
+ )
+ cur_state = p.current_state
+
+ @pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
+ @pytest.mark.parametrize(
+ "state_list",
+ [pytest.param(v, id=k) for k, v in PenState.legal_transitions().items()],
+ )
+ def test_valid_pen_states(self, state_list, scribble):
+ """This is the first half of the Windows Pen Implementation state machine:
+ we don't have Invert nor Erase bits, so just move in/out-of-range or proximity.
+ https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
+ """
+ self._test_states(state_list, scribble, allow_intermediate_states=False)
+
+ @pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
+ @pytest.mark.parametrize(
+ "state_list",
+ [
+ pytest.param(v, id=k)
+ for k, v in PenState.tolerated_transitions().items()
+ ],
+ )
+ def test_tolerated_pen_states(self, state_list, scribble):
+ """This is not adhering to the Windows Pen Implementation state machine
+ but we should expect the kernel to behave properly, mostly for historical
+ reasons."""
+ self._test_states(state_list, scribble, allow_intermediate_states=True)
+
+ @pytest.mark.skip_if_uhdev(
+ lambda uhdev: "Barrel Switch" not in uhdev.fields,
+ "Device not compatible, missing Barrel Switch usage",
+ )
+ @pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
+ @pytest.mark.parametrize(
+ "state_list",
+ [
+ pytest.param(v, id=k)
+ for k, v in PenState.legal_transitions_with_button().items()
+ ],
+ )
+ def test_valid_primary_button_pen_states(self, state_list, scribble):
+ """Rework the transition state machine by adding the primary button."""
+ self._test_states(
+ state_list,
+ scribble,
+ allow_intermediate_states=False,
+ button=BtnPressed.PRIMARY_PRESSED,
+ )
+
+ @pytest.mark.skip_if_uhdev(
+ lambda uhdev: "Secondary Barrel Switch" not in uhdev.fields,
+ "Device not compatible, missing Secondary Barrel Switch usage",
+ )
+ @pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
+ @pytest.mark.parametrize(
+ "state_list",
+ [
+ pytest.param(v, id=k)
+ for k, v in PenState.legal_transitions_with_button().items()
+ ],
+ )
+ def test_valid_secondary_button_pen_states(self, state_list, scribble):
+ """Rework the transition state machine by adding the secondary button."""
+ self._test_states(
+ state_list,
+ scribble,
+ allow_intermediate_states=False,
+ button=BtnPressed.SECONDARY_PRESSED,
+ )
+
+ @pytest.mark.skip_if_uhdev(
+ lambda uhdev: "Third Barrel Switch" not in uhdev.fields,
+ "Device not compatible, missing Third Barrel Switch usage",
+ )
+ @pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
+ @pytest.mark.parametrize(
+ "state_list",
+ [
+ pytest.param(v, id=k)
+ for k, v in PenState.legal_transitions_with_button().items()
+ ],
+ )
+ def test_valid_third_button_pen_states(self, state_list, scribble):
+ """Rework the transition state machine by adding the secondary button."""
+ self._test_states(
+ state_list,
+ scribble,
+ allow_intermediate_states=False,
+ button=BtnPressed.THIRD_PRESSED,
+ )
+
+ @pytest.mark.skip_if_uhdev(
+ lambda uhdev: "Invert" not in uhdev.fields,
+ "Device not compatible, missing Invert usage",
+ )
+ @pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
+ @pytest.mark.parametrize(
+ "state_list",
+ [
+ pytest.param(v, id=k)
+ for k, v in PenState.legal_transitions_with_invert().items()
+ ],
+ )
+ def test_valid_invert_pen_states(self, state_list, scribble):
+ """This is the second half of the Windows Pen Implementation state machine:
+ we now have Invert and Erase bits, so move in/out or proximity with the intend
+ to erase.
+ https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
+ """
+ self._test_states(state_list, scribble, allow_intermediate_states=False)
+
+ @pytest.mark.skip_if_uhdev(
+ lambda uhdev: "Invert" not in uhdev.fields,
+ "Device not compatible, missing Invert usage",
+ )
+ @pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
+ @pytest.mark.parametrize(
+ "state_list",
+ [
+ pytest.param(v, id=k)
+ for k, v in PenState.tolerated_transitions_with_invert().items()
+ ],
+ )
+ def test_tolerated_invert_pen_states(self, state_list, scribble):
+ """This is the second half of the Windows Pen Implementation state machine:
+ we now have Invert and Erase bits, so move in/out or proximity with the intend
+ to erase.
+ https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-pen-states
+ """
+ self._test_states(state_list, scribble, allow_intermediate_states=True)
+
+ @pytest.mark.skip_if_uhdev(
+ lambda uhdev: "Invert" not in uhdev.fields,
+ "Device not compatible, missing Invert usage",
+ )
+ @pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
+ @pytest.mark.parametrize(
+ "state_list",
+ [pytest.param(v, id=k) for k, v in PenState.broken_transitions().items()],
+ )
+ def test_tolerated_broken_pen_states(self, state_list, scribble):
+ """Those tests are definitely not part of the Windows specification.
+ However, a half broken device might export those transitions.
+ For example, a pen that has the eraser button might wobble between
+ touching and erasing if the tablet doesn't enforce the Windows
+ state machine."""
+ self._test_states(state_list, scribble, allow_intermediate_states=True)
+
+
+class GXTP_pen(PenDigitizer):
+ def event(self, pen, test_button):
+ if not hasattr(self, "prev_tip_state"):
+ self.prev_tip_state = False
+
+ internal_pen = copy.copy(pen)
+
+ # bug in the controller: when the pen touches the
+ # surface, in-range stays to 1, but when
+ # the pen moves in-range gets reverted to 0
+ if pen.tipswitch and self.prev_tip_state:
+ internal_pen.inrange = False
+
+ self.prev_tip_state = pen.tipswitch
+
+ # another bug in the controller: when the pen is
+ # inverted, invert is set to 1, but as soon as
+ # the pen touches the surface, eraser is correctly
+ # set to 1 but invert is released
+ if pen.eraser:
+ internal_pen.invert = False
+
+ return super().event(internal_pen, test_button)
+
+
+class USIPen(PenDigitizer):
+ pass
+
+
+class XPPen_ArtistPro16Gen2_28bd_095b(PenDigitizer):
+ """
+ Pen with two buttons and a rubber end, but which reports
+ the second button as an eraser
+ """
+
+ def __init__(
+ self,
+ name,
+ rdesc_str=None,
+ rdesc=None,
+ application="Pen",
+ physical="Stylus",
+ input_info=(BusType.USB, 0x28BD, 0x095B),
+ evdev_name_suffix=None,
+ ):
+ super().__init__(
+ name, rdesc_str, rdesc, application, physical, input_info, evdev_name_suffix
+ )
+ self.fields.append("Secondary Barrel Switch")
+
+ def move_to(self, pen, state, button):
+ # fill in the previous values
+ if pen.current_state == PenState.PEN_IS_OUT_OF_RANGE:
+ pen.restore()
+
+ print(f"\n *** pen is moving to {state} ***")
+
+ if state == PenState.PEN_IS_OUT_OF_RANGE:
+ pen.backup()
+ pen.x = 0
+ pen.y = 0
+ pen.tipswitch = False
+ pen.tippressure = 0
+ pen.azimuth = 0
+ pen.inrange = False
+ pen.width = 0
+ pen.height = 0
+ pen.invert = False
+ pen.eraser = False
+ pen.xtilt = 0
+ pen.ytilt = 0
+ pen.twist = 0
+ pen.barrelswitch = False
+ elif state == PenState.PEN_IS_IN_RANGE:
+ pen.tipswitch = False
+ pen.inrange = True
+ pen.invert = False
+ pen.eraser = False
+ pen.barrelswitch = False
+ elif state == PenState.PEN_IS_IN_CONTACT:
+ pen.tipswitch = True
+ pen.inrange = True
+ pen.invert = False
+ pen.eraser = False
+ pen.barrelswitch = False
+ elif state == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
+ pen.tipswitch = False
+ pen.inrange = True
+ pen.invert = False
+ assert button is not None
+ pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
+ pen.eraser = button == BtnPressed.SECONDARY_PRESSED
+ elif state == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
+ pen.tipswitch = True
+ pen.inrange = True
+ pen.invert = False
+ assert button is not None
+ pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
+ pen.eraser = button == BtnPressed.SECONDARY_PRESSED
+ elif state == PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT:
+ pen.tipswitch = False
+ pen.inrange = True
+ pen.invert = True
+ pen.eraser = False
+ pen.barrelswitch = False
+ elif state == PenState.PEN_IS_ERASING:
+ pen.tipswitch = True
+ pen.inrange = True
+ pen.invert = True
+ pen.eraser = False
+ pen.barrelswitch = False
+
+ pen.current_state = state
+
+ def event(self, pen, test_button):
+ import math
+
+ pen_copy = copy.copy(pen)
+ width = 13.567
+ height = 8.480
+ tip_height = 0.055677699
+ hx = tip_height * (32767 / width)
+ hy = tip_height * (32767 / height)
+ if pen_copy.xtilt != 0:
+ pen_copy.x += round(hx * math.sin(math.radians(pen_copy.xtilt)))
+ if pen_copy.ytilt != 0:
+ pen_copy.y += round(hy * math.sin(math.radians(pen_copy.ytilt)))
+
+ return super().event(pen_copy, test_button)
+
+
+class XPPen_Artist24_28bd_093a(PenDigitizer):
+ """
+ Pen that reports secondary barrel switch through eraser
+ """
+
+ def __init__(
+ self,
+ name,
+ rdesc_str=None,
+ rdesc=None,
+ application="Pen",
+ physical="Stylus",
+ input_info=(BusType.USB, 0x28BD, 0x093A),
+ evdev_name_suffix=None,
+ ):
+ super().__init__(
+ name, rdesc_str, rdesc, application, physical, input_info, evdev_name_suffix
+ )
+ self.fields.append("Secondary Barrel Switch")
+ self.previous_state = PenState.PEN_IS_OUT_OF_RANGE
+
+ def move_to(self, pen, state, button, debug=True):
+ # fill in the previous values
+ if pen.current_state == PenState.PEN_IS_OUT_OF_RANGE:
+ pen.restore()
+
+ if debug:
+ print(f"\n *** pen is moving to {state} ***")
+
+ if state == PenState.PEN_IS_OUT_OF_RANGE:
+ pen.backup()
+ pen.tipswitch = False
+ pen.tippressure = 0
+ pen.azimuth = 0
+ pen.inrange = False
+ pen.width = 0
+ pen.height = 0
+ pen.invert = False
+ pen.eraser = False
+ pen.xtilt = 0
+ pen.ytilt = 0
+ pen.twist = 0
+ pen.barrelswitch = False
+ elif state == PenState.PEN_IS_IN_RANGE:
+ pen.tipswitch = False
+ pen.inrange = True
+ pen.invert = False
+ pen.eraser = False
+ pen.barrelswitch = False
+ elif state == PenState.PEN_IS_IN_CONTACT:
+ pen.tipswitch = True
+ pen.inrange = True
+ pen.invert = False
+ pen.eraser = False
+ pen.barrelswitch = False
+ elif state == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
+ pen.tipswitch = False
+ pen.inrange = True
+ pen.invert = False
+ assert button is not None
+ pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
+ pen.eraser = button == BtnPressed.SECONDARY_PRESSED
+ elif state == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
+ pen.tipswitch = True
+ pen.inrange = True
+ pen.invert = False
+ assert button is not None
+ pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
+ pen.eraser = button == BtnPressed.SECONDARY_PRESSED
+
+ pen.current_state = state
+
+ def send_intermediate_state(self, pen, state, button):
+ intermediate_pen = copy.copy(pen)
+ self.move_to(intermediate_pen, state, button, debug=False)
+ return super().event(intermediate_pen, button)
+
+ def event(self, pen, button):
+ rs = []
+
+ # the pen reliably sends in-range events in a normal case (non emulation of eraser mode)
+ if self.previous_state == PenState.PEN_IS_IN_CONTACT:
+ if pen.current_state == PenState.PEN_IS_OUT_OF_RANGE:
+ rs.extend(
+ self.send_intermediate_state(pen, PenState.PEN_IS_IN_RANGE, button)
+ )
+
+ if button == BtnPressed.SECONDARY_PRESSED:
+ if self.previous_state == PenState.PEN_IS_IN_RANGE:
+ if pen.current_state == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
+ rs.extend(
+ self.send_intermediate_state(
+ pen, PenState.PEN_IS_OUT_OF_RANGE, button
+ )
+ )
+
+ if self.previous_state == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
+ if pen.current_state == PenState.PEN_IS_IN_RANGE:
+ rs.extend(
+ self.send_intermediate_state(
+ pen, PenState.PEN_IS_OUT_OF_RANGE, button
+ )
+ )
+
+ if self.previous_state == PenState.PEN_IS_IN_CONTACT:
+ if pen.current_state == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
+ rs.extend(
+ self.send_intermediate_state(
+ pen, PenState.PEN_IS_OUT_OF_RANGE, button
+ )
+ )
+ rs.extend(
+ self.send_intermediate_state(
+ pen, PenState.PEN_IS_IN_RANGE_WITH_BUTTON, button
+ )
+ )
+
+ if self.previous_state == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
+ if pen.current_state == PenState.PEN_IS_IN_CONTACT:
+ rs.extend(
+ self.send_intermediate_state(
+ pen, PenState.PEN_IS_OUT_OF_RANGE, button
+ )
+ )
+ rs.extend(
+ self.send_intermediate_state(
+ pen, PenState.PEN_IS_IN_RANGE, button
+ )
+ )
+
+ rs.extend(super().event(pen, button))
+ self.previous_state = pen.current_state
+ return rs
+
+
+class Huion_Kamvas_Pro_19_256c_006b(PenDigitizer):
+ """
+ Pen that reports secondary barrel switch through secondary TipSwtich
+ and 3rd button through Invert
+ """
+
+ def __init__(
+ self,
+ name,
+ rdesc_str=None,
+ rdesc=None,
+ application="Stylus",
+ physical=None,
+ input_info=(BusType.USB, 0x256C, 0x006B),
+ evdev_name_suffix=None,
+ ):
+ super().__init__(
+ name, rdesc_str, rdesc, application, physical, input_info, evdev_name_suffix
+ )
+ self.fields.append("Secondary Barrel Switch")
+ self.fields.append("Third Barrel Switch")
+ self.previous_state = PenState.PEN_IS_OUT_OF_RANGE
+
+ def move_to(self, pen, state, button, debug=True):
+ # fill in the previous values
+ if pen.current_state == PenState.PEN_IS_OUT_OF_RANGE:
+ pen.restore()
+
+ if debug:
+ print(f"\n *** pen is moving to {state} ***")
+
+ if state == PenState.PEN_IS_OUT_OF_RANGE:
+ pen.backup()
+ pen.tipswitch = False
+ pen.tippressure = 0
+ pen.azimuth = 0
+ pen.inrange = False
+ pen.width = 0
+ pen.height = 0
+ pen.invert = False
+ pen.eraser = False
+ pen.xtilt = 0
+ pen.ytilt = 0
+ pen.twist = 0
+ pen.barrelswitch = False
+ pen.secondarytipswitch = False
+ elif state == PenState.PEN_IS_IN_RANGE:
+ pen.tipswitch = False
+ pen.inrange = True
+ pen.invert = False
+ pen.eraser = False
+ pen.barrelswitch = False
+ pen.secondarytipswitch = False
+ elif state == PenState.PEN_IS_IN_CONTACT:
+ pen.tipswitch = True
+ pen.inrange = True
+ pen.invert = False
+ pen.eraser = False
+ pen.barrelswitch = False
+ pen.secondarytipswitch = False
+ elif state == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
+ pen.tipswitch = False
+ pen.inrange = True
+ pen.eraser = False
+ assert button is not None
+ pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
+ pen.secondarytipswitch = button == BtnPressed.SECONDARY_PRESSED
+ pen.invert = button == BtnPressed.THIRD_PRESSED
+ elif state == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
+ pen.tipswitch = True
+ pen.inrange = True
+ pen.eraser = False
+ assert button is not None
+ pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
+ pen.secondarytipswitch = button == BtnPressed.SECONDARY_PRESSED
+ pen.invert = button == BtnPressed.THIRD_PRESSED
+ elif state == PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT:
+ pen.tipswitch = False
+ pen.inrange = True
+ pen.invert = True
+ pen.eraser = False
+ pen.barrelswitch = False
+ pen.secondarytipswitch = False
+ elif state == PenState.PEN_IS_ERASING:
+ pen.tipswitch = False
+ pen.inrange = True
+ pen.invert = False
+ pen.eraser = True
+ pen.barrelswitch = False
+ pen.secondarytipswitch = False
+
+ pen.current_state = state
+
+ def call_input_event(self, report):
+ if report[0] == 0x0a:
+ # ensures the original second Eraser usage is null
+ report[1] &= 0xdf
+
+ # ensures the original last bit is equal to bit 6 (In Range)
+ if report[1] & 0x40:
+ report[1] |= 0x80
+
+ super().call_input_event(report)
+
+ def send_intermediate_state(self, pen, state, test_button):
+ intermediate_pen = copy.copy(pen)
+ self.move_to(intermediate_pen, state, test_button, debug=False)
+ return super().event(intermediate_pen, test_button)
+
+ def event(self, pen, button):
+ rs = []
+
+ # it's not possible to go between eraser mode or not without
+ # going out-of-prox: the eraser mode is activated by presenting
+ # the tail of the pen
+ if self.previous_state in (
+ PenState.PEN_IS_IN_RANGE,
+ PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
+ PenState.PEN_IS_IN_CONTACT,
+ PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
+ ) and pen.current_state in (
+ PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+ PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT_WITH_BUTTON,
+ PenState.PEN_IS_ERASING,
+ PenState.PEN_IS_ERASING_WITH_BUTTON,
+ ):
+ rs.extend(
+ self.send_intermediate_state(pen, PenState.PEN_IS_OUT_OF_RANGE, button)
+ )
+
+ # same than above except from eraser to normal
+ if self.previous_state in (
+ PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
+ PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT_WITH_BUTTON,
+ PenState.PEN_IS_ERASING,
+ PenState.PEN_IS_ERASING_WITH_BUTTON,
+ ) and pen.current_state in (
+ PenState.PEN_IS_IN_RANGE,
+ PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
+ PenState.PEN_IS_IN_CONTACT,
+ PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
+ ):
+ rs.extend(
+ self.send_intermediate_state(pen, PenState.PEN_IS_OUT_OF_RANGE, button)
+ )
+
+ if self.previous_state == PenState.PEN_IS_OUT_OF_RANGE:
+ if pen.current_state == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
+ rs.extend(
+ self.send_intermediate_state(pen, PenState.PEN_IS_IN_RANGE, button)
+ )
+
+ rs.extend(super().event(pen, button))
+ self.previous_state = pen.current_state
+ return rs
+
+
+################################################################################
+#
+# Windows 7 compatible devices
+#
+################################################################################
+# class TestEgalax_capacitive_0eef_7224(BaseTest.TestTablet):
+# def create_device(self):
+# return PenDigitizer('uhid test egalax-capacitive_0eef_7224',
+# rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 34 49 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 37 29 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 34 49 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 37 29 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0',
+# input_info=(BusType.USB, 0x0eef, 0x7224),
+# evdev_name_suffix=' Touchscreen')
+#
+#
+# class TestEgalax_capacitive_0eef_72fa(BaseTest.TestTablet):
+# def create_device(self):
+# return PenDigitizer('uhid test egalax-capacitive_0eef_72fa',
+# rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 72 22 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 87 13 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 72 22 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 87 13 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0',
+# input_info=(BusType.USB, 0x0eef, 0x72fa),
+# evdev_name_suffix=' Touchscreen')
+#
+#
+# class TestEgalax_capacitive_0eef_7336(BaseTest.TestTablet):
+# def create_device(self):
+# return PenDigitizer('uhid test egalax-capacitive_0eef_7336',
+# rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 c1 20 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 c2 18 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 c1 20 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 c2 18 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0',
+# input_info=(BusType.USB, 0x0eef, 0x7336),
+# evdev_name_suffix=' Touchscreen')
+#
+#
+# class TestEgalax_capacitive_0eef_7337(BaseTest.TestTablet):
+# def create_device(self):
+# return PenDigitizer('uhid test egalax-capacitive_0eef_7337',
+# rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 ae 17 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 c3 0e 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 ae 17 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 c3 0e 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0',
+# input_info=(BusType.USB, 0x0eef, 0x7337),
+# evdev_name_suffix=' Touchscreen')
+#
+#
+# class TestEgalax_capacitive_0eef_7349(BaseTest.TestTablet):
+# def create_device(self):
+# return PenDigitizer('uhid test egalax-capacitive_0eef_7349',
+# rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 34 49 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 37 29 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 34 49 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 37 29 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0',
+# input_info=(BusType.USB, 0x0eef, 0x7349),
+# evdev_name_suffix=' Touchscreen')
+#
+#
+# class TestEgalax_capacitive_0eef_73f4(BaseTest.TestTablet):
+# def create_device(self):
+# return PenDigitizer('uhid test egalax-capacitive_0eef_73f4',
+# rdesc='05 0d 09 04 a1 01 85 04 09 22 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 09 32 15 00 25 01 81 02 09 51 75 05 95 01 16 00 00 26 10 00 81 02 09 47 75 01 95 01 15 00 25 01 81 02 05 01 09 30 75 10 95 01 55 0d 65 33 35 00 46 96 4e 26 ff 7f 81 02 09 31 75 10 95 01 55 0d 65 33 35 00 46 23 2c 26 ff 7f 81 02 05 0d 09 55 25 08 75 08 95 01 b1 02 c0 c0 05 01 09 01 a1 01 85 01 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 01 75 06 81 01 05 01 09 30 09 31 16 00 00 26 ff 0f 36 00 00 46 ff 0f 66 00 00 75 10 95 02 81 02 c0 c0 06 00 ff 09 01 a1 01 09 01 15 00 26 ff 00 85 03 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0 05 0d 09 04 a1 01 85 02 09 20 a1 00 09 42 09 32 15 00 25 01 95 02 75 01 81 02 95 06 75 01 81 03 05 01 09 30 75 10 95 01 a4 55 0d 65 33 36 00 00 46 96 4e 16 00 00 26 ff 0f 81 02 09 31 16 00 00 26 ff 0f 36 00 00 46 23 2c 81 02 b4 c0 c0 05 0d 09 0e a1 01 85 05 09 22 a1 00 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0',
+# input_info=(BusType.USB, 0x0eef, 0x73f4),
+# evdev_name_suffix=' Touchscreen')
+#
+# bogus: BTN_TOOL_PEN is not emitted
+# class TestIrtouch_6615_0070(BaseTest.TestTablet):
+# def create_device(self):
+# return PenDigitizer('uhid test irtouch_6615_0070',
+# rdesc='05 01 09 02 a1 01 85 10 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 95 02 75 01 81 02 95 06 81 03 05 01 09 30 09 31 15 00 26 ff 7f 75 10 95 02 81 02 c0 c0 05 0d 09 04 a1 01 85 30 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 09 30 26 ff 7f 55 0f 65 11 35 00 46 51 02 75 10 95 01 81 02 09 31 35 00 46 73 01 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 09 51 75 08 95 01 81 02 05 01 09 30 26 ff 7f 55 0f 65 11 35 00 46 51 02 75 10 95 01 81 02 09 31 35 00 46 73 01 81 02 c0 05 0d 09 54 15 00 26 02 00 75 08 95 01 81 02 85 03 09 55 15 00 26 ff 00 75 08 95 01 b1 02 c0 05 0d 09 0e a1 01 85 02 09 52 09 53 15 00 26 ff 00 75 08 95 02 b1 02 c0 05 0d 09 02 a1 01 85 20 09 20 a1 00 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 03 05 01 09 30 26 ff 7f 55 0f 65 11 35 00 46 51 02 75 10 95 01 81 02 09 31 35 00 46 73 01 81 02 85 01 06 00 ff 09 01 75 08 95 01 b1 02 c0 c0',
+# input_info=(BusType.USB, 0x6615, 0x0070))
+
+
+class TestNexio_1870_0100(BaseTest.TestTablet):
+ def create_device(self):
+ return PenDigitizer(
+ "uhid test nexio_1870_0100",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 05 0d 09 54 95 01 75 08 25 02 81 02 85 02 09 55 25 02 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 95 03 75 01 19 01 29 03 15 00 25 01 81 02 95 01 75 05 81 01 05 01 75 10 95 02 09 30 09 31 15 00 26 ff 7f 81 02 c0 c0 05 0d 09 02 a1 01 85 05 09 20 a1 00 09 42 09 32 15 00 25 01 75 01 95 02 81 02 95 0e 81 03 05 01 26 ff 3f 75 10 95 01 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 c0 06 00 ff 09 01 a1 01 85 06 19 01 29 40 15 00 26 ff 00 75 08 95 40 81 00 19 01 29 40 91 00 c0",
+ input_info=(BusType.USB, 0x1870, 0x0100),
+ )
+
+
+class TestNexio_1870_010d(BaseTest.TestTablet):
+ def create_device(self):
+ return PenDigitizer(
+ "uhid test nexio_1870_010d",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 05 0d 09 54 95 01 75 08 25 02 81 02 85 02 09 55 25 06 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 95 03 75 01 19 01 29 03 15 00 25 01 81 02 95 01 75 05 81 01 05 01 75 10 95 02 09 30 09 31 15 00 26 ff 7f 81 02 c0 c0 05 0d 09 02 a1 01 85 05 09 20 a1 00 09 42 09 32 15 00 25 01 75 01 95 02 81 02 95 0e 81 03 05 01 26 ff 3f 75 10 95 01 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 c0 06 00 ff 09 01 a1 01 85 06 19 01 29 40 15 00 26 ff 00 75 08 95 3e 81 00 19 01 29 40 91 00 c0",
+ input_info=(BusType.USB, 0x1870, 0x010D),
+ )
+
+
+class TestNexio_1870_0119(BaseTest.TestTablet):
+ def create_device(self):
+ return PenDigitizer(
+ "uhid test nexio_1870_0119",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 95 06 81 03 75 08 09 51 95 01 81 02 05 01 26 ff 3f 75 10 55 0d 65 00 09 30 35 00 46 00 00 81 02 26 ff 3f 09 31 35 00 46 00 00 81 02 26 ff 3f 05 0d 09 48 35 00 26 ff 3f 81 02 09 49 35 00 26 ff 3f 81 02 c0 05 0d 09 54 95 01 75 08 25 02 81 02 85 02 09 55 25 06 b1 02 c0 09 0e a1 01 85 03 09 23 a1 02 09 52 09 53 15 00 25 0a 75 08 95 02 b1 02 c0 c0 05 01 09 02 a1 01 09 01 a1 00 85 04 05 09 95 03 75 01 19 01 29 03 15 00 25 01 81 02 95 01 75 05 81 01 05 01 75 10 95 02 09 30 09 31 15 00 26 ff 7f 81 02 c0 c0 05 0d 09 02 a1 01 85 05 09 20 a1 00 09 42 09 32 15 00 25 01 75 01 95 02 81 02 95 0e 81 03 05 01 26 ff 3f 75 10 95 01 55 0e 65 11 09 30 35 00 46 1e 19 81 02 26 ff 3f 09 31 35 00 46 be 0f 81 02 26 ff 3f c0 c0 06 00 ff 09 01 a1 01 85 06 19 01 29 40 15 00 26 ff 00 75 08 95 3e 81 00 19 01 29 40 91 00 c0",
+ input_info=(BusType.USB, 0x1870, 0x0119),
+ )
+
+
+################################################################################
+#
+# Windows 8 compatible devices
+#
+################################################################################
+
+# bogus: application is 'undefined'
+# class Testatmel_03eb_8409(BaseTest.TestTablet):
+# def create_device(self):
+# return PenDigitizer('uhid test atmel_03eb_8409', rdesc='05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 35 00 35 00 46 18 06 26 77 0f 09 31 81 02 35 00 35 00 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 48 81 02 09 49 81 02 c0 05 0d 27 ff ff 00 00 75 10 95 01 09 56 81 02 15 00 25 1f 75 05 09 54 95 01 81 02 75 03 25 01 95 01 81 03 75 08 85 02 09 55 25 10 b1 02 06 00 ff 85 05 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 00 a1 01 85 03 09 20 a1 00 15 00 25 01 75 01 95 01 09 42 81 02 09 44 81 02 09 45 81 02 81 03 09 32 81 02 95 03 81 03 05 01 55 0e 65 11 35 00 75 10 95 02 46 c8 0a 26 6f 08 09 30 81 02 46 18 06 26 77 0f 09 31 81 02 05 0d 09 30 15 01 26 ff 00 75 08 95 01 81 02 c0 c0')
+
+
+class Testatmel_03eb_840b(BaseTest.TestTablet):
+ def create_device(self):
+ return PenDigitizer(
+ "uhid test atmel_03eb_840b",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 95 01 81 03 25 1f 75 05 09 51 81 02 05 01 55 0e 65 11 35 00 75 10 95 01 46 00 0a 26 ff 0f 09 30 81 02 09 00 81 03 46 a0 05 26 ff 0f 09 31 81 02 09 00 81 03 05 0d 95 01 75 08 15 00 26 ff 00 46 ff 00 09 00 81 03 09 00 81 03 c0 05 0d 27 ff ff 00 00 75 10 95 01 09 56 81 02 15 00 25 1f 75 05 09 54 95 01 81 02 75 03 25 01 95 01 81 03 75 08 85 02 09 55 25 10 b1 02 06 00 ff 85 05 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 02 a1 01 85 03 09 20 a1 00 15 00 25 01 75 01 95 01 09 42 81 02 09 44 81 02 09 45 81 02 81 03 09 32 81 02 95 03 81 03 05 01 55 0e 65 11 35 00 75 10 95 02 46 00 0a 26 ff 0f 09 30 81 02 46 a0 05 26 ff 0f 09 31 81 02 05 0d 09 30 15 01 26 ff 00 75 08 95 01 81 02 c0 c0",
+ )
+
+
+class Testn_trig_1b96_0c01(BaseTest.TestTablet):
+ def create_device(self):
+ return PenDigitizer(
+ "uhid test n_trig_1b96_0c01",
+ rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 09 32 81 02 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0",
+ )
+
+
+class Testn_trig_1b96_0c03(BaseTest.TestTablet):
+ def create_device(self):
+ return PenDigitizer(
+ "uhid test n_trig_1b96_0c03",
+ rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 15 0a 26 80 25 81 02 09 31 46 b4 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0",
+ )
+
+
+class Testn_trig_1b96_0f00(BaseTest.TestTablet):
+ def create_device(self):
+ return PenDigitizer(
+ "uhid test n_trig_1b96_0f00",
+ rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0",
+ )
+
+
+class Testn_trig_1b96_0f04(BaseTest.TestTablet):
+ def create_device(self):
+ return PenDigitizer(
+ "uhid test n_trig_1b96_0f04",
+ rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 7f 0b 26 80 25 81 02 09 31 46 78 06 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 7f 0b 26 80 25 81 02 09 31 46 78 06 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 7f 0b 26 80 25 81 02 09 31 46 78 06 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0",
+ )
+
+
+class Testn_trig_1b96_1000(BaseTest.TestTablet):
+ def create_device(self):
+ return PenDigitizer(
+ "uhid test n_trig_1b96_1000",
+ rdesc="75 08 15 00 26 ff 00 06 0b ff 09 0b a1 01 95 0f 09 29 85 29 b1 02 95 1f 09 2a 85 2a b1 02 95 3e 09 2b 85 2b b1 02 95 fe 09 2c 85 2c b1 02 96 fe 01 09 2d 85 2d b1 02 95 02 09 48 85 48 b1 02 95 0f 09 2e 85 2e 81 02 95 1f 09 2f 85 2f 81 02 95 3e 09 30 85 30 81 02 95 fe 09 31 85 31 81 02 96 fe 01 09 32 85 32 81 02 75 08 96 fe 0f 09 35 85 35 81 02 c0 05 0d 09 02 a1 01 85 01 09 20 35 00 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 b4 05 0d 09 30 26 00 01 81 02 06 00 ff 09 01 81 02 c0 85 0c 06 00 ff 09 0c 75 08 95 06 26 ff 00 b1 02 85 0b 09 0b 95 02 b1 02 85 11 09 11 b1 02 85 15 09 15 95 05 b1 02 85 18 09 18 95 0c b1 02 c0 05 0d 09 04 a1 01 85 03 06 00 ff 09 01 75 10 95 01 15 00 27 ff ff 00 00 81 02 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 01 81 03 09 47 81 02 95 05 81 03 75 10 09 51 27 ff ff 00 00 95 01 81 02 05 01 09 30 75 10 95 02 a4 55 0e 65 11 46 03 0a 26 80 25 81 02 09 31 46 a1 05 26 20 1c 81 02 05 0d 09 48 95 01 26 80 25 81 02 09 49 26 20 1c 81 02 b4 06 00 ff 09 02 75 08 95 04 15 00 26 ff 00 81 02 c0 05 0d 09 54 95 01 75 08 81 02 09 56 75 20 95 01 27 ff ff ff 0f 81 02 85 04 09 55 75 08 95 01 25 0b b1 02 85 0a 06 00 ff 09 03 15 00 b1 02 85 1b 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 01 09 02 a1 01 85 02 09 01 a1 00 05 09 19 01 29 02 15 00 25 01 75 01 95 02 81 02 95 06 81 03 05 01 09 30 09 31 15 81 25 7f 75 08 95 02 81 06 c0 c0",
+ )
+
+
+class TestGXTP_27c6_0113(BaseTest.TestTablet):
+ def create_device(self):
+ return GXTP_pen(
+ "uhid test GXTP_27c6_0113",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 55 0e 65 11 35 00 15 00 09 42 25 01 75 01 95 01 81 02 95 07 81 01 95 01 75 08 09 51 81 02 75 10 05 01 26 00 14 46 1f 07 09 30 81 02 26 80 0c 46 77 04 09 31 81 02 05 0d c0 09 22 a1 02 09 42 25 01 75 01 95 01 81 02 95 07 81 01 95 01 75 08 09 51 81 02 75 10 05 01 26 00 14 46 1f 07 09 30 81 02 26 80 0c 46 77 04 09 31 81 02 05 0d c0 09 22 a1 02 09 42 25 01 75 01 95 01 81 02 95 07 81 01 95 01 75 08 09 51 81 02 75 10 05 01 26 00 14 46 1f 07 09 30 81 02 26 80 0c 46 77 04 09 31 81 02 05 0d c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 01 75 08 09 51 95 01 81 02 05 01 26 00 14 75 10 55 0e 65 11 09 30 35 00 46 1f 07 81 02 26 80 0c 46 77 04 09 31 81 02 05 0d c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 01 75 08 09 51 95 01 81 02 05 01 26 00 14 75 10 55 0e 65 11 09 30 35 00 46 1f 07 81 02 26 80 0c 46 77 04 09 31 81 02 05 0d c0 09 54 15 00 25 7f 75 08 95 01 81 02 85 02 09 55 95 01 25 0a b1 02 85 03 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 02 a1 01 85 08 09 20 a1 00 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 04 81 02 95 01 81 03 09 32 81 02 95 02 81 03 95 01 75 08 09 51 81 02 05 01 09 30 75 10 95 01 a4 55 0e 65 11 35 00 26 00 14 46 1f 07 81 42 09 31 26 80 0c 46 77 04 81 42 b4 05 0d 09 30 26 ff 0f 81 02 09 3d 65 14 55 0e 36 d8 dc 46 28 23 16 d8 dc 26 28 23 81 02 09 3e 81 02 c0 c0 06 f0 ff 09 01 a1 01 85 0e 09 01 15 00 25 ff 75 08 95 40 91 02 09 01 15 00 25 ff 75 08 95 40 81 02 c0 05 01 09 06 a1 01 85 04 05 07 09 e3 15 00 25 01 75 01 95 01 81 02 95 07 81 03 c0",
+ )
+
+
+################################################################################
+#
+# Windows 8 compatible devices with USI Pen
+#
+################################################################################
+
+
+class TestElan_04f3_2A49(BaseTest.TestTablet):
+ def create_device(self):
+ return USIPen(
+ "uhid test Elan_04f3_2A49",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 55 0f 65 11 35 00 45 ff 09 48 81 02 09 49 81 02 09 30 81 02 95 01 05 01 a4 26 cf 0f 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 01 81 02 26 77 0a 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 55 0f 65 11 35 00 45 ff 09 48 81 02 09 49 81 02 09 30 81 02 95 01 05 01 a4 26 cf 0f 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 01 81 02 26 77 0a 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 55 0f 65 11 35 00 45 ff 09 48 81 02 09 49 81 02 09 30 81 02 95 01 05 01 a4 26 cf 0f 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 01 81 02 26 77 0a 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 55 0f 65 11 35 00 45 ff 09 48 81 02 09 49 81 02 09 30 81 02 95 01 05 01 a4 26 cf 0f 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 01 81 02 26 77 0a 46 a6 00 09 31 81 02 b4 c0 05 0d 09 22 a1 02 05 0d 09 42 15 00 25 01 75 01 95 01 81 02 75 01 81 03 75 06 09 51 25 3f 81 02 26 ff 00 75 08 55 0f 65 11 35 00 45 ff 09 48 81 02 09 49 81 02 09 30 81 02 95 01 05 01 a4 26 cf 0f 75 10 55 0f 65 11 09 30 35 00 46 26 01 95 01 81 02 26 77 0a 46 a6 00 09 31 81 02 b4 c0 05 0d 09 54 25 7f 96 01 00 75 08 81 02 85 0a 09 55 25 0a b1 02 85 44 06 00 ff 09 c5 16 00 00 26 ff 00 75 08 96 00 01 b1 02 c0 06 ff 01 09 01 a1 01 85 02 16 00 00 26 ff 00 75 08 95 40 09 00 81 02 c0 06 00 ff 09 01 a1 01 85 03 75 08 95 20 09 01 91 02 c0 06 00 ff 09 01 a1 01 85 06 09 03 75 08 95 12 91 02 09 04 75 08 95 03 b1 02 c0 06 01 ff 09 01 a1 01 85 04 15 00 26 ff 00 75 08 95 13 09 00 81 02 c0 05 0d 09 02 a1 01 85 07 35 00 09 20 a1 00 09 32 09 42 09 44 09 3c 09 45 15 00 25 01 75 01 95 05 81 02 95 03 81 03 05 01 09 30 75 10 95 01 a4 55 0f 65 11 46 26 01 26 1c 48 81 42 09 31 46 a6 00 26 bc 2f 81 42 b4 05 0d 09 30 26 00 10 81 02 75 08 95 01 09 3b 25 64 81 42 09 38 15 00 25 02 81 02 09 5c 26 ff 00 81 02 09 5e 81 02 09 70 a1 02 15 01 25 06 09 72 09 73 09 74 09 75 09 76 09 77 81 20 09 5b 25 ff 75 40 81 02 c0 06 00 ff 75 08 95 02 09 01 81 02 c0 05 0d 85 60 09 81 a1 02 09 38 75 08 95 01 15 00 25 02 81 02 09 81 15 01 25 04 09 82 09 83 09 84 09 85 81 20 c0 85 61 09 5c a1 02 15 00 26 ff 00 75 08 95 01 09 38 b1 02 09 5c 26 ff 00 b1 02 09 5d 75 01 95 01 25 01 b1 02 95 07 b1 03 c0 85 62 09 5e a1 02 09 38 15 00 25 02 75 08 95 01 b1 02 09 5e 26 ff 00 b1 02 09 5f 75 01 25 01 b1 02 75 07 b1 03 c0 85 63 09 70 a1 02 75 08 95 01 15 00 25 02 09 38 b1 02 09 70 a1 02 25 06 09 72 09 73 09 74 09 75 09 76 09 77 b1 20 c0 09 71 75 01 25 01 b1 02 75 07 b1 03 c0 85 64 09 80 15 00 25 ff 75 40 95 01 b1 02 85 65 09 44 a1 02 09 38 75 08 95 01 25 02 b1 02 15 01 25 03 09 44 a1 02 09 a4 09 44 09 5a 09 45 09 a3 b1 20 c0 09 5a a1 02 09 a4 09 44 09 5a 09 45 09 a3 b1 20 c0 09 45 a1 02 09 a4 09 44 09 5a 09 45 09 a3 b1 20 c0 c0 85 66 75 08 95 01 05 0d 09 90 a1 02 09 38 25 02 b1 02 09 91 75 10 26 ff 0f b1 02 09 92 75 40 25 ff b1 02 05 06 09 2a 75 08 26 ff 00 a1 02 09 2d b1 02 09 2e b1 02 c0 c0 85 67 05 06 09 2b a1 02 05 0d 25 02 09 38 b1 02 05 06 09 2b a1 02 09 2d 26 ff 00 b1 02 09 2e b1 02 c0 c0 85 68 06 00 ff 09 01 a1 02 05 0d 09 38 75 08 95 01 25 02 b1 02 06 00 ff 09 01 75 10 27 ff ff 00 00 b1 02 c0 85 69 05 0d 09 38 75 08 95 01 15 00 25 02 b1 02 c0 06 00 ff 09 81 a1 01 85 17 75 08 95 1f 09 05 81 02 c0",
+ input_info=(BusType.I2C, 0x04F3, 0x2A49),
+ )
+
+
+class TestGoodix_27c6_0e00(BaseTest.TestTablet):
+ def create_device(self):
+ return USIPen(
+ "uhid test Elan_04f3_2A49",
+ rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 55 0e 65 11 35 00 15 00 09 42 25 01 75 01 95 01 81 02 25 7f 09 30 75 07 81 42 95 01 75 08 09 51 81 02 75 10 05 01 26 04 20 46 e6 09 09 30 81 02 26 60 15 46 9a 06 09 31 81 02 05 0d 55 0f 75 08 25 ff 45 ff 09 48 81 42 09 49 81 42 55 0e c0 09 22 a1 02 09 42 25 01 75 01 95 01 81 02 25 7f 09 30 75 07 81 42 95 01 75 08 09 51 81 02 75 10 05 01 26 04 20 46 e6 09 09 30 81 02 26 60 15 46 9a 06 09 31 81 02 05 0d 55 0f 75 08 25 ff 45 ff 09 48 81 42 09 49 81 42 55 0e c0 09 22 a1 02 09 42 25 01 75 01 95 01 81 02 25 7f 09 30 75 07 81 42 95 01 75 08 09 51 81 02 75 10 05 01 26 04 20 46 e6 09 09 30 81 02 26 60 15 46 9a 06 09 31 81 02 05 0d 55 0f 75 08 25 ff 45 ff 09 48 81 42 09 49 81 42 55 0e c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 30 75 07 81 42 75 08 09 51 95 01 81 02 05 01 26 04 20 75 10 55 0e 65 11 09 30 35 00 46 e6 09 81 02 26 60 15 46 9a 06 09 31 81 02 05 0d 55 0f 75 08 25 ff 45 ff 09 48 81 42 09 49 81 42 55 0e c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 30 75 07 81 42 75 08 09 51 95 01 81 02 05 01 26 04 20 75 10 55 0e 65 11 09 30 35 00 46 e6 09 81 02 26 60 15 46 9a 06 09 31 81 02 05 0d 55 0f 75 08 25 ff 45 ff 09 48 81 42 09 49 81 42 55 0e c0 09 54 15 00 25 7f 75 08 95 01 81 02 85 02 09 55 95 01 25 0a b1 02 85 03 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 02 a1 01 09 20 a1 00 85 08 05 01 a4 09 30 35 00 46 e6 09 15 00 26 04 20 55 0d 65 13 75 10 95 01 81 02 09 31 46 9a 06 26 60 15 81 02 b4 05 0d 09 38 95 01 75 08 15 00 25 01 81 02 09 30 75 10 26 ff 0f 81 02 09 31 81 02 09 42 09 44 09 5a 09 3c 09 45 09 32 75 01 95 06 25 01 81 02 95 02 81 03 09 3d 55 0e 65 14 36 d8 dc 46 28 23 16 d8 dc 26 28 23 95 01 75 10 81 02 09 3e 81 02 09 41 15 00 27 a0 8c 00 00 35 00 47 a0 8c 00 00 81 02 05 20 0a 53 04 65 00 16 01 f8 26 ff 07 75 10 95 01 81 02 0a 54 04 81 02 0a 55 04 81 02 0a 57 04 81 02 0a 58 04 81 02 0a 59 04 81 02 0a 72 04 81 02 0a 73 04 81 02 0a 74 04 81 02 05 0d 09 3b 15 00 25 64 75 08 81 02 09 5b 25 ff 75 40 81 02 06 00 ff 09 5b 75 20 81 02 05 0d 09 5c 26 ff 00 75 08 81 02 09 5e 81 02 09 70 a1 02 15 01 25 06 09 72 09 73 09 74 09 75 09 76 09 77 81 20 c0 06 00 ff 09 01 15 00 27 ff ff 00 00 75 10 95 01 81 02 85 09 09 81 a1 02 09 81 15 01 25 04 09 82 09 83 09 84 09 85 81 20 c0 85 10 09 5c a1 02 15 00 25 01 75 08 95 01 09 38 b1 02 09 5c 26 ff 00 b1 02 09 5d 75 01 95 01 25 01 b1 02 95 07 b1 03 c0 85 11 09 5e a1 02 09 38 15 00 25 01 75 08 95 01 b1 02 09 5e 26 ff 00 b1 02 09 5f 75 01 25 01 b1 02 75 07 b1 03 c0 85 12 09 70 a1 02 75 08 95 01 15 00 25 01 09 38 b1 02 09 70 a1 02 25 06 09 72 09 73 09 74 09 75 09 76 09 77 b1 20 c0 09 71 75 01 25 01 b1 02 75 07 b1 03 c0 85 13 09 80 15 00 25 ff 75 40 95 01 b1 02 85 14 09 44 a1 02 09 38 75 08 95 01 25 01 b1 02 15 01 25 03 09 44 a1 02 09 a4 09 44 09 5a 09 45 09 a3 b1 20 c0 09 5a a1 02 09 a4 09 44 09 5a 09 45 09 a3 b1 20 c0 09 45 a1 02 09 a4 09 44 09 5a 09 45 09 a3 b1 20 c0 c0 85 15 75 08 95 01 05 0d 09 90 a1 02 09 38 25 01 b1 02 09 91 75 10 26 ff 0f b1 02 09 92 75 40 25 ff b1 02 05 06 09 2a 75 08 26 ff 00 a1 02 09 2d b1 02 09 2e b1 02 c0 c0 85 16 05 06 09 2b a1 02 05 0d 25 01 09 38 b1 02 05 06 09 2b a1 02 09 2d 26 ff 00 b1 02 09 2e b1 02 c0 c0 85 17 06 00 ff 09 01 a1 02 05 0d 09 38 75 08 95 01 25 01 b1 02 06 00 ff 09 01 75 10 27 ff ff 00 00 b1 02 c0 85 18 05 0d 09 38 75 08 95 01 15 00 25 01 b1 02 c0 c0 06 f0 ff 09 01 a1 01 85 0e 09 01 15 00 25 ff 75 08 95 40 91 02 09 01 15 00 25 ff 75 08 95 40 81 02 c0",
+ input_info=(BusType.I2C, 0x27C6, 0x0E00),
+ )
+
+
+class TestXPPen_ArtistPro16Gen2_28bd_095b(BaseTest.TestTablet):
+ hid_bpfs = [("XPPen__ArtistPro16Gen2.bpf.o", True)]
+
+ def create_device(self):
+ dev = XPPen_ArtistPro16Gen2_28bd_095b(
+ "uhid test XPPen Artist Pro 16 Gen2 28bd 095b",
+ rdesc="05 0d 09 02 a1 01 85 07 09 20 a1 00 09 42 09 44 09 45 09 3c 15 00 25 01 75 01 95 04 81 02 95 01 81 03 09 32 15 00 25 01 95 01 81 02 95 02 81 03 75 10 95 01 35 00 a4 05 01 09 30 65 13 55 0d 46 ff 34 26 ff 7f 81 02 09 31 46 20 21 26 ff 7f 81 02 b4 09 30 45 00 26 ff 3f 81 42 09 3d 15 81 25 7f 75 08 95 01 81 02 09 3e 15 81 25 7f 81 02 c0 c0",
+ input_info=(BusType.USB, 0x28BD, 0x095B),
+ )
+ return dev
+
+
+class TestXPPen_Artist24_28bd_093a(BaseTest.TestTablet):
+ hid_bpfs = [("XPPen__Artist24.bpf.o", True)]
+
+ def create_device(self):
+ return XPPen_Artist24_28bd_093a(
+ "uhid test XPPen Artist 24 28bd 093a",
+ rdesc="05 0d 09 02 a1 01 85 07 09 20 a1 00 09 42 09 44 09 45 15 00 25 01 75 01 95 03 81 02 95 02 81 03 09 32 95 01 81 02 95 02 81 03 75 10 95 01 35 00 a4 05 01 09 30 65 13 55 0d 46 f0 50 26 ff 7f 81 02 09 31 46 91 2d 26 ff 7f 81 02 b4 09 30 45 00 26 ff 1f 81 42 09 3d 15 81 25 7f 75 08 95 01 81 02 09 3e 15 81 25 7f 81 02 c0 c0",
+ input_info=(BusType.USB, 0x28BD, 0x093A),
+ )
+
+
+class TestHuion_Kamvas_Pro_19_256c_006b(BaseTest.TestTablet):
+ hid_bpfs = [("Huion__Kamvas-Pro-19.bpf.o", True)]
+
+ def create_device(self):
+ return Huion_Kamvas_Pro_19_256c_006b(
+ "uhid test HUION Huion Tablet_GT1902",
+ rdesc="05 0d 09 02 a1 01 85 0a 09 20 a1 01 09 42 09 44 09 43 09 3c 09 45 15 00 25 01 75 01 95 06 81 02 09 32 75 01 95 01 81 02 81 03 05 01 09 30 09 31 55 0d 65 33 26 ff 7f 35 00 46 00 08 75 10 95 02 81 02 05 0d 09 30 26 ff 3f 75 10 95 01 81 02 09 3d 09 3e 15 a6 25 5a 75 08 95 02 81 02 c0 c0 05 0d 09 04 a1 01 85 04 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 ff 7f 35 00 46 15 0c 81 42 09 31 26 ff 7f 46 cb 06 81 42 05 0d 09 30 26 ff 1f 75 10 95 01 81 02 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 ff 7f 35 00 46 15 0c 81 42 09 31 26 ff 7f 46 cb 06 81 42 05 0d 09 30 26 ff 1f 75 10 95 01 81 02 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 75 08 95 08 81 03 85 05 09 55 25 0a 75 08 95 01 b1 02 06 00 ff 09 c5 85 06 15 00 26 ff 00 75 08 96 00 01 b1 02 c0",
+ input_info=(BusType.USB, 0x256C, 0x006B),
+ )
diff --git a/tools/testing/selftests/hid/tests/test_usb_crash.py b/tools/testing/selftests/hid/tests/test_usb_crash.py
new file mode 100644
index 000000000000..e98bff9197c7
--- /dev/null
+++ b/tools/testing/selftests/hid/tests/test_usb_crash.py
@@ -0,0 +1,103 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2021 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2021 Red Hat, Inc.
+#
+
+# This is to ensure we don't crash when emulating USB devices
+
+from . import base
+import pytest
+import logging
+
+logger = logging.getLogger("hidtools.test.usb")
+
+
+class USBDev(base.UHIDTestDevice):
+ # fmt: off
+ report_descriptor = [
+ 0x05, 0x01, # .Usage Page (Generic Desktop) 0
+ 0x09, 0x02, # .Usage (Mouse) 2
+ 0xa1, 0x01, # .Collection (Application) 4
+ 0x09, 0x02, # ..Usage (Mouse) 6
+ 0xa1, 0x02, # ..Collection (Logical) 8
+ 0x09, 0x01, # ...Usage (Pointer) 10
+ 0xa1, 0x00, # ...Collection (Physical) 12
+ 0x05, 0x09, # ....Usage Page (Button) 14
+ 0x19, 0x01, # ....Usage Minimum (1) 16
+ 0x29, 0x03, # ....Usage Maximum (3) 18
+ 0x15, 0x00, # ....Logical Minimum (0) 20
+ 0x25, 0x01, # ....Logical Maximum (1) 22
+ 0x75, 0x01, # ....Report Size (1) 24
+ 0x95, 0x03, # ....Report Count (3) 26
+ 0x81, 0x02, # ....Input (Data,Var,Abs) 28
+ 0x75, 0x05, # ....Report Size (5) 30
+ 0x95, 0x01, # ....Report Count (1) 32
+ 0x81, 0x03, # ....Input (Cnst,Var,Abs) 34
+ 0x05, 0x01, # ....Usage Page (Generic Desktop) 36
+ 0x09, 0x30, # ....Usage (X) 38
+ 0x09, 0x31, # ....Usage (Y) 40
+ 0x15, 0x81, # ....Logical Minimum (-127) 42
+ 0x25, 0x7f, # ....Logical Maximum (127) 44
+ 0x75, 0x08, # ....Report Size (8) 46
+ 0x95, 0x02, # ....Report Count (2) 48
+ 0x81, 0x06, # ....Input (Data,Var,Rel) 50
+ 0xc0, # ...End Collection 52
+ 0xc0, # ..End Collection 53
+ 0xc0, # .End Collection 54
+ ]
+ # fmt: on
+
+ def __init__(self, name=None, input_info=None):
+ super().__init__(
+ name, "Mouse", input_info=input_info, rdesc=USBDev.report_descriptor
+ )
+
+ # skip witing for udev events, it's likely that the report
+ # descriptor is wrong
+ def is_ready(self):
+ return True
+
+ # we don't have an evdev node here, so paper over
+ # the checks
+ def get_evdev(self, application=None):
+ return "OK"
+
+
+class TestUSBDevice(base.BaseTestCase.TestUhid):
+ """
+ Test class to test if an emulated USB device crashes
+ the kernel.
+ """
+
+ # conftest.py is generating the following fixture:
+ #
+ # @pytest.fixture(params=[('modulename', 1, 2)])
+ # def usbVidPid(self, request):
+ # return request.param
+
+ @pytest.fixture()
+ def new_uhdev(self, usbVidPid, request):
+ self.module, self.vid, self.pid = usbVidPid
+ self._load_kernel_module(None, self.module)
+ return USBDev(input_info=(3, self.vid, self.pid))
+
+ def test_creation(self):
+ """
+ inject the USB dev through uhid and immediately see if there is a crash:
+
+ uhid can create a USB device with the BUS_USB bus, and some
+ drivers assume that they can then access USB related structures
+ when they are actually provided a uhid device. This leads to
+ a crash because those access result in a segmentation fault.
+
+ The kernel should not crash on any (random) user space correct
+ use of its API. So run through all available modules and declared
+ devices to see if we can generate a uhid device without a crash.
+
+ The test is empty as the fixture `check_taint` is doing the job (and
+ honestly, when the kernel crashes, the whole machine freezes).
+ """
+ assert True
diff --git a/tools/testing/selftests/hid/tests/test_wacom_generic.py b/tools/testing/selftests/hid/tests/test_wacom_generic.py
new file mode 100644
index 000000000000..b62c7dba6777
--- /dev/null
+++ b/tools/testing/selftests/hid/tests/test_wacom_generic.py
@@ -0,0 +1,1198 @@
+#!/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+# Copyright (c) 2017 Red Hat, Inc.
+# Copyright (c) 2020 Wacom Technology Corp.
+#
+# Authors:
+# Jason Gerecke <jason.gerecke@wacom.com>
+
+"""
+Tests for the Wacom driver generic codepath.
+
+This module tests the function of the Wacom driver's generic codepath.
+The generic codepath is used by devices which are not explicitly listed
+in the driver's device table. It uses the device's HID descriptor to
+decode reports sent by the device.
+"""
+
+from .descriptors_wacom import (
+ wacom_pth660_v145,
+ wacom_pth660_v150,
+ wacom_pth860_v145,
+ wacom_pth860_v150,
+ wacom_pth460_v105,
+)
+
+import attr
+from collections import namedtuple
+from enum import Enum
+from hidtools.hut import HUT
+from hidtools.hid import HidUnit
+from . import base
+from . import test_multitouch
+import libevdev
+import pytest
+
+import logging
+
+logger = logging.getLogger("hidtools.test.wacom")
+
+KERNEL_MODULE = ("wacom", "wacom")
+
+
+class ProximityState(Enum):
+ """
+ Enumeration of allowed proximity states.
+ """
+
+ # Tool is not able to be sensed by the device
+ OUT = 0
+
+ # Tool is close enough to be sensed, but some data may be invalid
+ # or inaccurate
+ IN_PROXIMITY = 1
+
+ # Tool is close enough to be sensed with high accuracy. All data
+ # valid.
+ IN_RANGE = 2
+
+ def fill(self, reportdata):
+ """Fill a report with approrpiate HID properties/values."""
+ reportdata.inrange = self in [ProximityState.IN_RANGE]
+ reportdata.wacomsense = self in [
+ ProximityState.IN_PROXIMITY,
+ ProximityState.IN_RANGE,
+ ]
+
+
+class ReportData:
+ """
+ Placeholder for HID report values.
+ """
+
+ pass
+
+
+@attr.s
+class Buttons:
+ """
+ Stylus button state.
+
+ Describes the state of each of the buttons / "side switches" that
+ may be present on a stylus. Buttons set to 'None' indicate the
+ state is "unchanged" since the previous event.
+ """
+
+ primary = attr.ib(default=None)
+ secondary = attr.ib(default=None)
+ tertiary = attr.ib(default=None)
+
+ @staticmethod
+ def clear():
+ """Button object with all states cleared."""
+ return Buttons(False, False, False)
+
+ def fill(self, reportdata):
+ """Fill a report with approrpiate HID properties/values."""
+ reportdata.barrelswitch = int(self.primary or 0)
+ reportdata.secondarybarrelswitch = int(self.secondary or 0)
+ reportdata.b3 = int(self.tertiary or 0)
+
+
+@attr.s
+class ToolID:
+ """
+ Stylus tool identifiers.
+
+ Contains values used to identify a specific stylus, e.g. its serial
+ number and tool-type identifier. Values of ``0`` may sometimes be
+ used for the out-of-range condition.
+ """
+
+ serial = attr.ib()
+ tooltype = attr.ib()
+
+ @staticmethod
+ def clear():
+ """ToolID object with all fields cleared."""
+ return ToolID(0, 0)
+
+ def fill(self, reportdata):
+ """Fill a report with approrpiate HID properties/values."""
+ reportdata.transducerserialnumber = self.serial & 0xFFFFFFFF
+ reportdata.serialhi = (self.serial >> 32) & 0xFFFFFFFF
+ reportdata.tooltype = self.tooltype
+
+
+@attr.s
+class PhysRange:
+ """
+ Range of HID physical values, with units.
+ """
+
+ unit = attr.ib()
+ min_size = attr.ib()
+ max_size = attr.ib()
+
+ CENTIMETER = HidUnit.from_string("SILinear: cm")
+ DEGREE = HidUnit.from_string("EnglishRotation: deg")
+
+ def contains(self, field):
+ """
+ Check if the physical size of the provided field is in range.
+
+ Compare the physical size described by the provided HID field
+ against the range of sizes described by this object. This is
+ an exclusive range comparison (e.g. 0 cm is not within the
+ range 0 cm - 5 cm) and exact unit comparison (e.g. 1 inch is
+ not within the range 0 cm - 5 cm).
+ """
+ phys_size = (field.physical_max - field.physical_min) * 10 ** (field.unit_exp)
+ return (
+ field.unit == self.unit.value
+ and phys_size > self.min_size
+ and phys_size < self.max_size
+ )
+
+
+class BaseTablet(base.UHIDTestDevice):
+ """
+ Skeleton object for all kinds of tablet devices.
+ """
+
+ def __init__(self, rdesc, name=None, info=None):
+ assert rdesc is not None
+ super().__init__(name, "Pen", input_info=info, rdesc=rdesc)
+ self.buttons = Buttons.clear()
+ self.toolid = ToolID.clear()
+ self.proximity = ProximityState.OUT
+ self.offset = 0
+ self.ring = -1
+ self.ek0 = False
+
+ def match_evdev_rule(self, application, evdev):
+ """
+ Filter out evdev nodes based on the requested application.
+
+ The Wacom driver may create several device nodes for each USB
+ interface device. It is crucial that we run tests with the
+ expected device node or things will obviously go off the rails.
+ Use the Wacom driver's usual naming conventions to apply a
+ sensible default filter.
+ """
+ if application in ["Pen", "Pad"]:
+ return evdev.name.endswith(application)
+ else:
+ return True
+
+ def create_report(
+ self, x, y, pressure, buttons=None, toolid=None, proximity=None, reportID=None
+ ):
+ """
+ Return an input report for this device.
+
+ :param x: absolute x
+ :param y: absolute y
+ :param pressure: pressure
+ :param buttons: stylus button state. Use ``None`` for unchanged.
+ :param toolid: tool identifiers. Use ``None`` for unchanged.
+ :param proximity: a ProximityState indicating the sensor's ability
+ to detect and report attributes of this tool. Use ``None``
+ for unchanged.
+ :param reportID: the numeric report ID for this report, if needed
+ """
+ if buttons is not None:
+ self.buttons = buttons
+ buttons = self.buttons
+
+ if toolid is not None:
+ self.toolid = toolid
+ toolid = self.toolid
+
+ if proximity is not None:
+ self.proximity = proximity
+ proximity = self.proximity
+
+ reportID = reportID or self.default_reportID
+
+ report = ReportData()
+ report.x = x
+ report.y = y
+ report.tippressure = pressure
+ report.tipswitch = pressure > 0
+ buttons.fill(report)
+ proximity.fill(report)
+ toolid.fill(report)
+
+ return super().create_report(report, reportID=reportID)
+
+ def create_report_heartbeat(self, reportID):
+ """
+ Return a heartbeat input report for this device.
+
+ Heartbeat reports generally contain battery status information,
+ among other things.
+ """
+ report = ReportData()
+ report.wacombatterycharging = 1
+ return super().create_report(report, reportID=reportID)
+
+ def create_report_pad(self, reportID, ring, ek0):
+ report = ReportData()
+
+ if ring is not None:
+ self.ring = ring
+ ring = self.ring
+
+ if ek0 is not None:
+ self.ek0 = ek0
+ ek0 = self.ek0
+
+ if ring >= 0:
+ report.wacomtouchring = ring
+ report.wacomtouchringstatus = 1
+ else:
+ report.wacomtouchring = 0x7F
+ report.wacomtouchringstatus = 0
+
+ report.wacomexpresskey00 = ek0
+ return super().create_report(report, reportID=reportID)
+
+ def event(self, x, y, pressure, buttons=None, toolid=None, proximity=None):
+ """
+ Send an input event on the default report ID.
+
+ :param x: absolute x
+ :param y: absolute y
+ :param buttons: stylus button state. Use ``None`` for unchanged.
+ :param toolid: tool identifiers. Use ``None`` for unchanged.
+ :param proximity: a ProximityState indicating the sensor's ability
+ to detect and report attributes of this tool. Use ``None``
+ for unchanged.
+ """
+ r = self.create_report(x, y, pressure, buttons, toolid, proximity)
+ self.call_input_event(r)
+ return [r]
+
+ def event_heartbeat(self, reportID):
+ """
+ Send a heartbeat event on the requested report ID.
+ """
+ r = self.create_report_heartbeat(reportID)
+ self.call_input_event(r)
+ return [r]
+
+ def event_pad(self, reportID, ring=None, ek0=None):
+ """
+ Send a pad event on the requested report ID.
+ """
+ r = self.create_report_pad(reportID, ring, ek0)
+ self.call_input_event(r)
+ return [r]
+
+ def get_report(self, req, rnum, rtype):
+ if rtype != self.UHID_FEATURE_REPORT:
+ return (1, [])
+
+ rdesc = None
+ for v in self.parsed_rdesc.feature_reports.values():
+ if v.report_ID == rnum:
+ rdesc = v
+
+ if rdesc is None:
+ return (1, [])
+
+ result = (1, [])
+ result = self.create_report_offset(rdesc) or result
+ return result
+
+ def create_report_offset(self, rdesc):
+ require = [
+ "Wacom Offset Left",
+ "Wacom Offset Top",
+ "Wacom Offset Right",
+ "Wacom Offset Bottom",
+ ]
+ if not set(require).issubset(set([f.usage_name for f in rdesc])):
+ return None
+
+ report = ReportData()
+ report.wacomoffsetleft = self.offset
+ report.wacomoffsettop = self.offset
+ report.wacomoffsetright = self.offset
+ report.wacomoffsetbottom = self.offset
+ r = rdesc.create_report([report], None)
+ return (0, r)
+
+
+class OpaqueTablet(BaseTablet):
+ """
+ Bare-bones opaque tablet with a minimum of features.
+
+ A tablet stripped down to its absolute core. It is capable of
+ reporting X/Y position and if the pen is in contact. No pressure,
+ no barrel switches, no eraser. Notably it *does* report an "In
+ Range" flag, but this is only because the Wacom driver expects
+ one to function properly. The device uses only standard HID usages,
+ not any of Wacom's vendor-defined pages.
+ """
+
+ # fmt: off
+ report_descriptor = [
+ 0x05, 0x0D, # . Usage Page (Digitizer),
+ 0x09, 0x01, # . Usage (Digitizer),
+ 0xA1, 0x01, # . Collection (Application),
+ 0x85, 0x01, # . Report ID (1),
+ 0x09, 0x20, # . Usage (Stylus),
+ 0xA1, 0x00, # . Collection (Physical),
+ 0x09, 0x42, # . Usage (Tip Switch),
+ 0x09, 0x32, # . Usage (In Range),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0x75, 0x01, # . Report Size (1),
+ 0x95, 0x02, # . Report Count (2),
+ 0x81, 0x02, # . Input (Variable),
+ 0x95, 0x06, # . Report Count (6),
+ 0x81, 0x03, # . Input (Constant, Variable),
+ 0x05, 0x01, # . Usage Page (Desktop),
+ 0x09, 0x30, # . Usage (X),
+ 0x27, 0x80, 0x3E, 0x00, 0x00, # . Logical Maximum (16000),
+ 0x47, 0x80, 0x3E, 0x00, 0x00, # . Physical Maximum (16000),
+ 0x65, 0x11, # . Unit (Centimeter),
+ 0x55, 0x0D, # . Unit Exponent (13),
+ 0x75, 0x10, # . Report Size (16),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0x09, 0x31, # . Usage (Y),
+ 0x27, 0x28, 0x23, 0x00, 0x00, # . Logical Maximum (9000),
+ 0x47, 0x28, 0x23, 0x00, 0x00, # . Physical Maximum (9000),
+ 0x81, 0x02, # . Input (Variable),
+ 0xC0, # . End Collection,
+ 0xC0, # . End Collection,
+ ]
+ # fmt: on
+
+ def __init__(self, rdesc=report_descriptor, name=None, info=(0x3, 0x056A, 0x9999)):
+ super().__init__(rdesc, name, info)
+ self.default_reportID = 1
+
+
+class OpaqueCTLTablet(BaseTablet):
+ """
+ Opaque tablet similar to something in the CTL product line.
+
+ A pen-only tablet with most basic features you would expect from
+ an actual device. Position, eraser, pressure, barrel buttons.
+ Uses the Wacom vendor-defined usage page.
+ """
+
+ # fmt: off
+ report_descriptor = [
+ 0x06, 0x0D, 0xFF, # . Usage Page (Vnd Wacom Emr),
+ 0x09, 0x01, # . Usage (Digitizer),
+ 0xA1, 0x01, # . Collection (Application),
+ 0x85, 0x10, # . Report ID (16),
+ 0x09, 0x20, # . Usage (Stylus),
+ 0x35, 0x00, # . Physical Minimum (0),
+ 0x45, 0x00, # . Physical Maximum (0),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0xA1, 0x00, # . Collection (Physical),
+ 0x09, 0x42, # . Usage (Tip Switch),
+ 0x09, 0x44, # . Usage (Barrel Switch),
+ 0x09, 0x5A, # . Usage (Secondary Barrel Switch),
+ 0x09, 0x45, # . Usage (Eraser),
+ 0x09, 0x3C, # . Usage (Invert),
+ 0x09, 0x32, # . Usage (In Range),
+ 0x09, 0x36, # . Usage (In Proximity),
+ 0x25, 0x01, # . Logical Maximum (1),
+ 0x75, 0x01, # . Report Size (1),
+ 0x95, 0x07, # . Report Count (7),
+ 0x81, 0x02, # . Input (Variable),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x03, # . Input (Constant, Variable),
+ 0x0A, 0x30, 0x01, # . Usage (X),
+ 0x65, 0x11, # . Unit (Centimeter),
+ 0x55, 0x0D, # . Unit Exponent (13),
+ 0x47, 0x80, 0x3E, 0x00, 0x00, # . Physical Maximum (16000),
+ 0x27, 0x80, 0x3E, 0x00, 0x00, # . Logical Maximum (16000),
+ 0x75, 0x18, # . Report Size (24),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0x0A, 0x31, 0x01, # . Usage (Y),
+ 0x47, 0x28, 0x23, 0x00, 0x00, # . Physical Maximum (9000),
+ 0x27, 0x28, 0x23, 0x00, 0x00, # . Logical Maximum (9000),
+ 0x81, 0x02, # . Input (Variable),
+ 0x09, 0x30, # . Usage (Tip Pressure),
+ 0x55, 0x00, # . Unit Exponent (0),
+ 0x65, 0x00, # . Unit,
+ 0x47, 0x00, 0x00, 0x00, 0x00, # . Physical Maximum (0),
+ 0x26, 0xFF, 0x0F, # . Logical Maximum (4095),
+ 0x75, 0x10, # . Report Size (16),
+ 0x81, 0x02, # . Input (Variable),
+ 0x75, 0x08, # . Report Size (8),
+ 0x95, 0x06, # . Report Count (6),
+ 0x81, 0x03, # . Input (Constant, Variable),
+ 0x0A, 0x32, 0x01, # . Usage (Z),
+ 0x25, 0x3F, # . Logical Maximum (63),
+ 0x75, 0x08, # . Report Size (8),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0x09, 0x5B, # . Usage (Transducer Serial Number),
+ 0x09, 0x5C, # . Usage (Transducer Serial Number Hi),
+ 0x17, 0x00, 0x00, 0x00, 0x80, # . Logical Minimum (-2147483648),
+ 0x27, 0xFF, 0xFF, 0xFF, 0x7F, # . Logical Maximum (2147483647),
+ 0x75, 0x20, # . Report Size (32),
+ 0x95, 0x02, # . Report Count (2),
+ 0x81, 0x02, # . Input (Variable),
+ 0x09, 0x77, # . Usage (Tool Type),
+ 0x15, 0x00, # . Logical Minimum (0),
+ 0x26, 0xFF, 0x0F, # . Logical Maximum (4095),
+ 0x75, 0x10, # . Report Size (16),
+ 0x95, 0x01, # . Report Count (1),
+ 0x81, 0x02, # . Input (Variable),
+ 0xC0, # . End Collection,
+ 0xC0 # . End Collection
+ ]
+ # fmt: on
+
+ def __init__(self, rdesc=report_descriptor, name=None, info=(0x3, 0x056A, 0x9999)):
+ super().__init__(rdesc, name, info)
+ self.default_reportID = 16
+
+
+class PTHX60_Pen(BaseTablet):
+ """
+ Pen interface of a PTH-660 / PTH-860 / PTH-460 tablet.
+
+ This generation of devices are nearly identical to each other, though
+ the PTH-460 uses a slightly different descriptor construction (splits
+ the pad among several physical collections)
+ """
+
+ def __init__(self, rdesc=None, name=None, info=None):
+ super().__init__(rdesc, name, info)
+ self.default_reportID = 16
+
+
+class BaseTest:
+ class TestTablet(base.BaseTestCase.TestUhid):
+ kernel_modules = [KERNEL_MODULE]
+
+ def sync_and_assert_events(
+ self, report, expected_events, auto_syn=True, strict=False
+ ):
+ """
+ Assert we see the expected events in response to a report.
+ """
+ uhdev = self.uhdev
+ syn_event = self.syn_event
+ if auto_syn:
+ expected_events.append(syn_event)
+ actual_events = uhdev.next_sync_events()
+ self.debug_reports(report, uhdev, actual_events)
+ if strict:
+ self.assertInputEvents(expected_events, actual_events)
+ else:
+ self.assertInputEventsIn(expected_events, actual_events)
+
+ def get_usages(self, uhdev):
+ def get_report_usages(report):
+ application = report.application
+ for field in report.fields:
+ if field.usages is not None:
+ for usage in field.usages:
+ yield (field, usage, application)
+ else:
+ yield (field, field.usage, application)
+
+ desc = uhdev.parsed_rdesc
+ reports = [
+ *desc.input_reports.values(),
+ *desc.feature_reports.values(),
+ *desc.output_reports.values(),
+ ]
+ for report in reports:
+ for usage in get_report_usages(report):
+ yield usage
+
+ def assertName(self, uhdev, type):
+ """
+ Assert that the name is as we expect.
+
+ The Wacom driver applies a number of decorations to the name
+ provided by the hardware. We cannot rely on the definition of
+ this assertion from the base class to work properly.
+ """
+ evdev = uhdev.get_evdev()
+ expected_name = uhdev.name + type
+ if "wacom" not in expected_name.lower():
+ expected_name = "Wacom " + expected_name
+ assert evdev.name == expected_name
+
+ def test_descriptor_physicals(self):
+ """
+ Verify that all HID usages which should have a physical range
+ actually do, and those which shouldn't don't. Also verify that
+ the associated unit is correct and within a sensible range.
+ """
+
+ def usage_id(page_name, usage_name):
+ page = HUT.usage_page_from_name(page_name)
+ return (page.page_id << 16) | page[usage_name].usage
+
+ required = {
+ usage_id("Generic Desktop", "X"): PhysRange(
+ PhysRange.CENTIMETER, 5, 150
+ ),
+ usage_id("Generic Desktop", "Y"): PhysRange(
+ PhysRange.CENTIMETER, 5, 150
+ ),
+ usage_id("Digitizers", "Width"): PhysRange(
+ PhysRange.CENTIMETER, 5, 150
+ ),
+ usage_id("Digitizers", "Height"): PhysRange(
+ PhysRange.CENTIMETER, 5, 150
+ ),
+ usage_id("Digitizers", "X Tilt"): PhysRange(PhysRange.DEGREE, 90, 180),
+ usage_id("Digitizers", "Y Tilt"): PhysRange(PhysRange.DEGREE, 90, 180),
+ usage_id("Digitizers", "Twist"): PhysRange(PhysRange.DEGREE, 358, 360),
+ usage_id("Wacom", "X Tilt"): PhysRange(PhysRange.DEGREE, 90, 180),
+ usage_id("Wacom", "Y Tilt"): PhysRange(PhysRange.DEGREE, 90, 180),
+ usage_id("Wacom", "Twist"): PhysRange(PhysRange.DEGREE, 358, 360),
+ usage_id("Wacom", "X"): PhysRange(PhysRange.CENTIMETER, 5, 150),
+ usage_id("Wacom", "Y"): PhysRange(PhysRange.CENTIMETER, 5, 150),
+ usage_id("Wacom", "Wacom TouchRing"): PhysRange(
+ PhysRange.DEGREE, 358, 360
+ ),
+ usage_id("Wacom", "Wacom Offset Left"): PhysRange(
+ PhysRange.CENTIMETER, 0, 0.5
+ ),
+ usage_id("Wacom", "Wacom Offset Top"): PhysRange(
+ PhysRange.CENTIMETER, 0, 0.5
+ ),
+ usage_id("Wacom", "Wacom Offset Right"): PhysRange(
+ PhysRange.CENTIMETER, 0, 0.5
+ ),
+ usage_id("Wacom", "Wacom Offset Bottom"): PhysRange(
+ PhysRange.CENTIMETER, 0, 0.5
+ ),
+ }
+ for field, usage, application in self.get_usages(self.uhdev):
+ if application == usage_id("Generic Desktop", "Mouse"):
+ # Ignore the vestigial Mouse collection which exists
+ # on Wacom tablets only for backwards compatibility.
+ continue
+
+ expect_physical = usage in required
+
+ phys_set = field.physical_min != 0 or field.physical_max != 0
+ assert phys_set == expect_physical
+
+ unit_set = field.unit != 0
+ assert unit_set == expect_physical
+
+ if unit_set:
+ assert required[usage].contains(field)
+
+ def test_prop_direct(self):
+ """
+ Todo: Verify that INPUT_PROP_DIRECT is set on display devices.
+ """
+ pass
+
+ def test_prop_pointer(self):
+ """
+ Todo: Verify that INPUT_PROP_POINTER is set on opaque devices.
+ """
+ pass
+
+
+class PenTabletTest(BaseTest.TestTablet):
+ def assertName(self, uhdev):
+ super().assertName(uhdev, " Pen")
+
+
+class TouchTabletTest(BaseTest.TestTablet):
+ def assertName(self, uhdev):
+ super().assertName(uhdev, " Finger")
+
+
+class TestOpaqueTablet(PenTabletTest):
+ def create_device(self):
+ return OpaqueTablet()
+
+ def test_sanity(self):
+ """
+ Bring a pen into contact with the tablet, then remove it.
+
+ Ensure that we get the basic tool/touch/motion events that should
+ be sent by the driver.
+ """
+ uhdev = self.uhdev
+
+ self.sync_and_assert_events(
+ uhdev.event(
+ 100,
+ 200,
+ pressure=300,
+ buttons=Buttons.clear(),
+ toolid=ToolID(serial=1, tooltype=1),
+ proximity=ProximityState.IN_RANGE,
+ ),
+ [
+ libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 1),
+ libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 100),
+ libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 200),
+ libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1),
+ ],
+ )
+
+ self.sync_and_assert_events(
+ uhdev.event(110, 220, pressure=0),
+ [
+ libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 110),
+ libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 220),
+ libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0),
+ ],
+ )
+
+ self.sync_and_assert_events(
+ uhdev.event(
+ 120,
+ 230,
+ pressure=0,
+ toolid=ToolID.clear(),
+ proximity=ProximityState.OUT,
+ ),
+ [
+ libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 0),
+ ],
+ )
+
+ self.sync_and_assert_events(
+ uhdev.event(130, 240, pressure=0), [], auto_syn=False, strict=True
+ )
+
+
+class TestOpaqueCTLTablet(TestOpaqueTablet):
+ def create_device(self):
+ return OpaqueCTLTablet()
+
+ def test_buttons(self):
+ """
+ Test that the barrel buttons (side switches) work as expected.
+
+ Press and release each button individually to verify that we get
+ the expected events.
+ """
+ uhdev = self.uhdev
+
+ self.sync_and_assert_events(
+ uhdev.event(
+ 100,
+ 200,
+ pressure=0,
+ buttons=Buttons.clear(),
+ toolid=ToolID(serial=1, tooltype=1),
+ proximity=ProximityState.IN_RANGE,
+ ),
+ [
+ libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 1),
+ libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 100),
+ libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 200),
+ libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1),
+ ],
+ )
+
+ self.sync_and_assert_events(
+ uhdev.event(100, 200, pressure=0, buttons=Buttons(primary=True)),
+ [
+ libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS, 1),
+ libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1),
+ ],
+ )
+
+ self.sync_and_assert_events(
+ uhdev.event(100, 200, pressure=0, buttons=Buttons(primary=False)),
+ [
+ libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS, 0),
+ libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1),
+ ],
+ )
+
+ self.sync_and_assert_events(
+ uhdev.event(100, 200, pressure=0, buttons=Buttons(secondary=True)),
+ [
+ libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS2, 1),
+ libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1),
+ ],
+ )
+
+ self.sync_and_assert_events(
+ uhdev.event(100, 200, pressure=0, buttons=Buttons(secondary=False)),
+ [
+ libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS2, 0),
+ libevdev.InputEvent(libevdev.EV_MSC.MSC_SERIAL, 1),
+ ],
+ )
+
+
+PTHX60_Devices = [
+ {"rdesc": wacom_pth660_v145, "info": (0x3, 0x056A, 0x0357)},
+ {"rdesc": wacom_pth660_v150, "info": (0x3, 0x056A, 0x0357)},
+ {"rdesc": wacom_pth860_v145, "info": (0x3, 0x056A, 0x0358)},
+ {"rdesc": wacom_pth860_v150, "info": (0x3, 0x056A, 0x0358)},
+ {"rdesc": wacom_pth460_v105, "info": (0x3, 0x056A, 0x0392)},
+]
+
+PTHX60_Names = [
+ "PTH-660/v145",
+ "PTH-660/v150",
+ "PTH-860/v145",
+ "PTH-860/v150",
+ "PTH-460/v105",
+]
+
+
+class TestPTHX60_Pen(TestOpaqueCTLTablet):
+ @pytest.fixture(
+ autouse=True, scope="class", params=PTHX60_Devices, ids=PTHX60_Names
+ )
+ def set_device_params(self, request):
+ request.cls.device_params = request.param
+
+ def create_device(self):
+ return PTHX60_Pen(**self.device_params)
+
+ @pytest.mark.xfail
+ def test_descriptor_physicals(self):
+ # XFAIL: Various documented errata
+ super().test_descriptor_physicals()
+
+ def test_heartbeat_spurious(self):
+ """
+ Test that the heartbeat report does not send spurious events.
+ """
+ uhdev = self.uhdev
+
+ self.sync_and_assert_events(
+ uhdev.event(
+ 100,
+ 200,
+ pressure=300,
+ buttons=Buttons.clear(),
+ toolid=ToolID(serial=1, tooltype=0x822),
+ proximity=ProximityState.IN_RANGE,
+ ),
+ [
+ libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, 1),
+ libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 100),
+ libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, 200),
+ libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1),
+ ],
+ )
+
+ # Exactly zero events: not even a SYN
+ self.sync_and_assert_events(
+ uhdev.event_heartbeat(19), [], auto_syn=False, strict=True
+ )
+
+ self.sync_and_assert_events(
+ uhdev.event(110, 200, pressure=300),
+ [
+ libevdev.InputEvent(libevdev.EV_ABS.ABS_X, 110),
+ ],
+ )
+
+ def test_empty_pad_sync(self):
+ self.empty_pad_sync(num=3, denom=16, reverse=True)
+
+ def empty_pad_sync(self, num, denom, reverse):
+ """
+ Test that multiple pad collections do not trigger empty syncs.
+ """
+
+ def offset_rotation(value):
+ """
+ Offset touchring rotation values by the same factor as the
+ Linux kernel. Tablets historically don't use the same origin
+ as HID, and it sometimes changes from tablet to tablet...
+ """
+ evdev = self.uhdev.get_evdev()
+ info = evdev.absinfo[libevdev.EV_ABS.ABS_WHEEL]
+ delta = info.maximum - info.minimum + 1
+ if reverse:
+ value = info.maximum - value
+ value += num * delta // denom
+ if value > info.maximum:
+ value -= delta
+ elif value < info.minimum:
+ value += delta
+ return value
+
+ uhdev = self.uhdev
+ uhdev.application = "Pad"
+ evdev = uhdev.get_evdev()
+
+ print(evdev.name)
+ self.sync_and_assert_events(
+ uhdev.event_pad(reportID=17, ring=0, ek0=1),
+ [
+ libevdev.InputEvent(libevdev.EV_KEY.BTN_0, 1),
+ libevdev.InputEvent(libevdev.EV_ABS.ABS_WHEEL, offset_rotation(0)),
+ libevdev.InputEvent(libevdev.EV_ABS.ABS_MISC, 15),
+ ],
+ )
+
+ self.sync_and_assert_events(
+ uhdev.event_pad(reportID=17, ring=1, ek0=1),
+ [libevdev.InputEvent(libevdev.EV_ABS.ABS_WHEEL, offset_rotation(1))],
+ )
+
+ self.sync_and_assert_events(
+ uhdev.event_pad(reportID=17, ring=2, ek0=0),
+ [
+ libevdev.InputEvent(libevdev.EV_ABS.ABS_WHEEL, offset_rotation(2)),
+ libevdev.InputEvent(libevdev.EV_KEY.BTN_0, 0),
+ ],
+ )
+
+
+class TestDTH2452Tablet(test_multitouch.BaseTest.TestMultitouch, TouchTabletTest):
+ ContactIds = namedtuple("ContactIds", "contact_id, tracking_id, slot_num")
+
+ def create_device(self):
+ return test_multitouch.Digitizer(
+ "DTH 2452",
+ rdesc="05 0d 09 04 a1 01 85 0c 95 01 75 08 15 00 26 ff 00 81 03 09 54 81 02 09 22 a1 02 05 0d 95 01 75 01 25 01 09 42 81 02 81 03 09 47 81 02 95 05 81 03 09 51 26 ff 00 75 10 95 01 81 02 35 00 65 11 55 0e 05 01 09 30 26 a0 44 46 96 14 81 42 09 31 26 9a 26 46 95 0b 81 42 05 0d 75 08 95 01 15 00 09 48 26 5f 00 46 7c 14 81 02 09 49 25 35 46 7d 0b 81 02 45 00 65 00 55 00 c0 05 0d 09 22 a1 02 05 0d 95 01 75 01 25 01 09 42 81 02 81 03 09 47 81 02 95 05 81 03 09 51 26 ff 00 75 10 95 01 81 02 35 00 65 11 55 0e 05 01 09 30 26 a0 44 46 96 14 81 42 09 31 26 9a 26 46 95 0b 81 42 05 0d 75 08 95 01 15 00 09 48 26 5f 00 46 7c 14 81 02 09 49 25 35 46 7d 0b 81 02 45 00 65 00 55 00 c0 05 0d 09 22 a1 02 05 0d 95 01 75 01 25 01 09 42 81 02 81 03 09 47 81 02 95 05 81 03 09 51 26 ff 00 75 10 95 01 81 02 35 00 65 11 55 0e 05 01 09 30 26 a0 44 46 96 14 81 42 09 31 26 9a 26 46 95 0b 81 42 05 0d 75 08 95 01 15 00 09 48 26 5f 00 46 7c 14 81 02 09 49 25 35 46 7d 0b 81 02 45 00 65 00 55 00 c0 05 0d 09 22 a1 02 05 0d 95 01 75 01 25 01 09 42 81 02 81 03 09 47 81 02 95 05 81 03 09 51 26 ff 00 75 10 95 01 81 02 35 00 65 11 55 0e 05 01 09 30 26 a0 44 46 96 14 81 42 09 31 26 9a 26 46 95 0b 81 42 05 0d 75 08 95 01 15 00 09 48 26 5f 00 46 7c 14 81 02 09 49 25 35 46 7d 0b 81 02 45 00 65 00 55 00 c0 05 0d 09 22 a1 02 05 0d 95 01 75 01 25 01 09 42 81 02 81 03 09 47 81 02 95 05 81 03 09 51 26 ff 00 75 10 95 01 81 02 35 00 65 11 55 0e 05 01 09 30 26 a0 44 46 96 14 81 42 09 31 26 9a 26 46 95 0b 81 42 05 0d 75 08 95 01 15 00 09 48 26 5f 00 46 7c 14 81 02 09 49 25 35 46 7d 0b 81 02 45 00 65 00 55 00 c0 05 0d 27 ff ff 00 00 75 10 95 01 09 56 81 02 75 08 95 0e 81 03 09 55 26 ff 00 75 08 b1 02 85 0a 06 00 ff 09 c5 96 00 01 b1 02 c0 06 00 ff 09 01 a1 01 09 01 85 13 15 00 26 ff 00 75 08 95 3f 81 02 06 00 ff 09 01 15 00 26 ff 00 75 08 95 3f 91 02 c0",
+ input_info=(0x3, 0x056A, 0x0383),
+ )
+
+ def make_contact(self, contact_id=0, t=0):
+ """
+ Make a single touch contact that can move over time.
+
+ Creates a touch object that has a well-known position in space that
+ does not overlap with other contacts. The value of `t` may be
+ incremented over time to move the point along a linear path.
+ """
+ x = 50 + 10 * contact_id + t * 11
+ y = 100 + 100 * contact_id + t * 11
+ return test_multitouch.Touch(contact_id, x, y)
+
+ def make_contacts(self, n, t=0):
+ """
+ Make multiple touch contacts that can move over time.
+
+ Returns a list of `n` touch objects that are positioned at well-known
+ locations. The value of `t` may be incremented over time to move the
+ points along a linear path.
+ """
+ return [ self.make_contact(id, t) for id in range(0, n) ]
+
+ def assert_contact(self, uhdev, evdev, contact_ids, t=0):
+ """
+ Assert properties of a contact generated by make_contact.
+ """
+ contact_id = contact_ids.contact_id
+ tracking_id = contact_ids.tracking_id
+ slot_num = contact_ids.slot_num
+
+ x = 50 + 10 * contact_id + t * 11
+ y = 100 + 100 * contact_id + t * 11
+
+ # If the data isn't supposed to be stored in any slots, there is
+ # nothing we can check for in the evdev stream.
+ if slot_num is None:
+ assert tracking_id == -1
+ return
+
+ assert evdev.slots[slot_num][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == tracking_id
+ if tracking_id != -1:
+ assert evdev.slots[slot_num][libevdev.EV_ABS.ABS_MT_POSITION_X] == x
+ assert evdev.slots[slot_num][libevdev.EV_ABS.ABS_MT_POSITION_Y] == y
+
+ def assert_contacts(self, uhdev, evdev, data, t=0):
+ """
+ Assert properties of a list of contacts generated by make_contacts.
+ """
+ for contact_ids in data:
+ self.assert_contact(uhdev, evdev, contact_ids, t)
+
+ def test_contact_id_0(self):
+ """
+ Bring a finger in contact with the tablet, then hold it down and remove it.
+
+ Ensure that even with contact ID = 0 which is usually given as an invalid
+ touch event by most tablets with the exception of a few, that given the
+ confidence bit is set to 1 it should process it as a valid touch to cover
+ the few tablets using contact ID = 0 as a valid touch value.
+ """
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+
+ t0 = test_multitouch.Touch(0, 50, 100)
+ r = uhdev.event([t0])
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+
+ slot = self.get_slot(uhdev, t0, 0)
+
+ assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
+ assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
+ assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
+ assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
+
+ t0.tipswitch = False
+ if uhdev.quirks is None or "VALID_IS_INRANGE" not in uhdev.quirks:
+ t0.inrange = False
+ r = uhdev.event([t0])
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+ assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events
+ assert evdev.slots[slot][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
+
+ def test_confidence_false(self):
+ """
+ Bring a finger in contact with the tablet with confidence set to false.
+
+ Ensure that the confidence bit being set to false should not result in a touch event.
+ """
+ uhdev = self.uhdev
+ _evdev = uhdev.get_evdev()
+
+ t0 = test_multitouch.Touch(1, 50, 100)
+ t0.confidence = False
+ r = uhdev.event([t0])
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+
+ _slot = self.get_slot(uhdev, t0, 0)
+
+ assert not events
+
+ def test_confidence_multitouch(self):
+ """
+ Bring multiple fingers in contact with the tablet, some with the
+ confidence bit set, and some without.
+
+ Ensure that all confident touches are reported and that all non-
+ confident touches are ignored.
+ """
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+
+ touches = self.make_contacts(5)
+ touches[0].confidence = False
+ touches[2].confidence = False
+ touches[4].confidence = False
+
+ r = uhdev.event(touches)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+
+ assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
+
+ self.assert_contacts(uhdev, evdev,
+ [ self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = None),
+ self.ContactIds(contact_id = 1, tracking_id = 0, slot_num = 0),
+ self.ContactIds(contact_id = 2, tracking_id = -1, slot_num = None),
+ self.ContactIds(contact_id = 3, tracking_id = 1, slot_num = 1),
+ self.ContactIds(contact_id = 4, tracking_id = -1, slot_num = None) ])
+
+ def confidence_change_assert_playback(self, uhdev, evdev, timeline):
+ """
+ Assert proper behavior of contacts that move and change tipswitch /
+ confidence status over time.
+
+ Given a `timeline` list of touch states to iterate over, verify
+ that the contacts move and are reported as up/down as expected
+ by the state of the tipswitch and confidence bits.
+ """
+ t = 0
+
+ for state in timeline:
+ touches = self.make_contacts(len(state), t)
+
+ for item in zip(touches, state):
+ item[0].tipswitch = item[1][1]
+ item[0].confidence = item[1][2]
+
+ r = uhdev.event(touches)
+ events = uhdev.next_sync_events()
+ self.debug_reports(r, uhdev, events)
+
+ ids = [ x[0] for x in state ]
+ self.assert_contacts(uhdev, evdev, ids, t)
+
+ t += 1
+
+ def test_confidence_loss_a(self):
+ """
+ Transition a confident contact to a non-confident contact by
+ first clearing the tipswitch.
+
+ Ensure that the driver reports the transitioned contact as
+ being removed and that other contacts continue to report
+ normally. This mode of confidence loss is used by the
+ DTH-2452.
+ """
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+
+ self.confidence_change_assert_playback(uhdev, evdev, [
+ # t=0: Contact 0 == Down + confident; Contact 1 == Down + confident
+ # Both fingers confidently in contact
+ [(self.ContactIds(contact_id = 0, tracking_id = 0, slot_num = 0), True, True),
+ (self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)],
+
+ # t=1: Contact 0 == !Down + confident; Contact 1 == Down + confident
+ # First finger looses confidence and clears only the tipswitch flag
+ [(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), False, True),
+ (self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)],
+
+ # t=2: Contact 0 == !Down + !confident; Contact 1 == Down + confident
+ # First finger has lost confidence and has both flags cleared
+ [(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), False, False),
+ (self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)],
+
+ # t=3: Contact 0 == !Down + !confident; Contact 1 == Down + confident
+ # First finger has lost confidence and has both flags cleared
+ [(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), False, False),
+ (self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)]
+ ])
+
+ def test_confidence_loss_b(self):
+ """
+ Transition a confident contact to a non-confident contact by
+ cleraing both tipswitch and confidence bits simultaneously.
+
+ Ensure that the driver reports the transitioned contact as
+ being removed and that other contacts continue to report
+ normally. This mode of confidence loss is used by some
+ AES devices.
+ """
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+
+ self.confidence_change_assert_playback(uhdev, evdev, [
+ # t=0: Contact 0 == Down + confident; Contact 1 == Down + confident
+ # Both fingers confidently in contact
+ [(self.ContactIds(contact_id = 0, tracking_id = 0, slot_num = 0), True, True),
+ (self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)],
+
+ # t=1: Contact 0 == !Down + !confident; Contact 1 == Down + confident
+ # First finger looses confidence and has both flags cleared simultaneously
+ [(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), False, False),
+ (self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)],
+
+ # t=2: Contact 0 == !Down + !confident; Contact 1 == Down + confident
+ # First finger has lost confidence and has both flags cleared
+ [(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), False, False),
+ (self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)],
+
+ # t=3: Contact 0 == !Down + !confident; Contact 1 == Down + confident
+ # First finger has lost confidence and has both flags cleared
+ [(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), False, False),
+ (self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)]
+ ])
+
+ def test_confidence_loss_c(self):
+ """
+ Transition a confident contact to a non-confident contact by
+ clearing only the confidence bit.
+
+ Ensure that the driver reports the transitioned contact as
+ being removed and that other contacts continue to report
+ normally.
+ """
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+
+ self.confidence_change_assert_playback(uhdev, evdev, [
+ # t=0: Contact 0 == Down + confident; Contact 1 == Down + confident
+ # Both fingers confidently in contact
+ [(self.ContactIds(contact_id = 0, tracking_id = 0, slot_num = 0), True, True),
+ (self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)],
+
+ # t=1: Contact 0 == Down + !confident; Contact 1 == Down + confident
+ # First finger looses confidence and clears only the confidence flag
+ [(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), True, False),
+ (self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)],
+
+ # t=2: Contact 0 == !Down + !confident; Contact 1 == Down + confident
+ # First finger has lost confidence and has both flags cleared
+ [(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), False, False),
+ (self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)],
+
+ # t=3: Contact 0 == !Down + !confident; Contact 1 == Down + confident
+ # First finger has lost confidence and has both flags cleared
+ [(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), False, False),
+ (self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)]
+ ])
+
+ def test_confidence_gain_a(self):
+ """
+ Transition a contact that was always non-confident to confident.
+
+ Ensure that the confident contact is reported normally.
+ """
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+
+ self.confidence_change_assert_playback(uhdev, evdev, [
+ # t=0: Contact 0 == Down + !confident; Contact 1 == Down + confident
+ # Only second finger is confidently in contact
+ [(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = None), True, False),
+ (self.ContactIds(contact_id = 1, tracking_id = 0, slot_num = 0), True, True)],
+
+ # t=1: Contact 0 == Down + !confident; Contact 1 == Down + confident
+ # First finger gains confidence
+ [(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = None), True, False),
+ (self.ContactIds(contact_id = 1, tracking_id = 0, slot_num = 0), True, True)],
+
+ # t=2: Contact 0 == Down + confident; Contact 1 == Down + confident
+ # First finger remains confident
+ [(self.ContactIds(contact_id = 0, tracking_id = 1, slot_num = 1), True, True),
+ (self.ContactIds(contact_id = 1, tracking_id = 0, slot_num = 0), True, True)],
+
+ # t=3: Contact 0 == Down + confident; Contact 1 == Down + confident
+ # First finger remains confident
+ [(self.ContactIds(contact_id = 0, tracking_id = 1, slot_num = 1), True, True),
+ (self.ContactIds(contact_id = 1, tracking_id = 0, slot_num = 0), True, True)]
+ ])
+
+ def test_confidence_gain_b(self):
+ """
+ Transition a contact from non-confident to confident.
+
+ Ensure that the confident contact is reported normally.
+ """
+ uhdev = self.uhdev
+ evdev = uhdev.get_evdev()
+
+ self.confidence_change_assert_playback(uhdev, evdev, [
+ # t=0: Contact 0 == Down + confident; Contact 1 == Down + confident
+ # First and second finger confidently in contact
+ [(self.ContactIds(contact_id = 0, tracking_id = 0, slot_num = 0), True, True),
+ (self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)],
+
+ # t=1: Contact 0 == Down + !confident; Contact 1 == Down + confident
+ # Firtst finger looses confidence
+ [(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), True, False),
+ (self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)],
+
+ # t=2: Contact 0 == Down + confident; Contact 1 == Down + confident
+ # First finger gains confidence
+ [(self.ContactIds(contact_id = 0, tracking_id = 2, slot_num = 0), True, True),
+ (self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)],
+
+ # t=3: Contact 0 == !Down + confident; Contact 1 == Down + confident
+ # First finger goes up
+ [(self.ContactIds(contact_id = 0, tracking_id = -1, slot_num = 0), False, True),
+ (self.ContactIds(contact_id = 1, tracking_id = 1, slot_num = 1), True, True)]
+ ])
diff --git a/tools/testing/selftests/hid/vmtest.sh b/tools/testing/selftests/hid/vmtest.sh
new file mode 100755
index 000000000000..db534e9099a8
--- /dev/null
+++ b/tools/testing/selftests/hid/vmtest.sh
@@ -0,0 +1,296 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+set -u
+set -e
+
+# This script currently only works for x86_64
+ARCH="$(uname -m)"
+case "${ARCH}" in
+x86_64)
+ QEMU_BINARY=qemu-system-x86_64
+ BZIMAGE="arch/x86/boot/bzImage"
+ ;;
+*)
+ echo "Unsupported architecture"
+ exit 1
+ ;;
+esac
+SCRIPT_DIR="$(dirname $(realpath $0))"
+OUTPUT_DIR="$SCRIPT_DIR/results"
+KCONFIG_REL_PATHS=("${SCRIPT_DIR}/config" "${SCRIPT_DIR}/config.common" "${SCRIPT_DIR}/config.${ARCH}")
+B2C_URL="https://gitlab.freedesktop.org/gfx-ci/boot2container/-/raw/main/vm2c.py"
+NUM_COMPILE_JOBS="$(nproc)"
+LOG_FILE_BASE="$(date +"hid_selftests.%Y-%m-%d_%H-%M-%S")"
+LOG_FILE="${LOG_FILE_BASE}.log"
+EXIT_STATUS_FILE="${LOG_FILE_BASE}.exit_status"
+CONTAINER_IMAGE="registry.freedesktop.org/bentiss/hid/fedora/39:2023-11-22.1"
+
+TARGETS="${TARGETS:=$(basename ${SCRIPT_DIR})}"
+DEFAULT_COMMAND="pip3 install hid-tools; make -C tools/testing/selftests TARGETS=${TARGETS} run_tests"
+
+usage()
+{
+ cat <<EOF
+Usage: $0 [-j N] [-s] [-b] [-d <output_dir>] -- [<command>]
+
+<command> is the command you would normally run when you are in
+the source kernel direcory. e.g:
+
+ $0 -- ./tools/testing/selftests/hid/hid_bpf
+
+If no command is specified and a debug shell (-s) is not requested,
+"${DEFAULT_COMMAND}" will be run by default.
+
+If you build your kernel using KBUILD_OUTPUT= or O= options, these
+can be passed as environment variables to the script:
+
+ O=<kernel_build_path> $0 -- ./tools/testing/selftests/hid/hid_bpf
+
+or
+
+ KBUILD_OUTPUT=<kernel_build_path> $0 -- ./tools/testing/selftests/hid/hid_bpf
+
+Options:
+
+ -u) Update the boot2container script to a newer version.
+ -d) Update the output directory (default: ${OUTPUT_DIR})
+ -b) Run only the build steps for the kernel and the selftests
+ -j) Number of jobs for compilation, similar to -j in make
+ (default: ${NUM_COMPILE_JOBS})
+ -s) Instead of powering off the VM, start an interactive
+ shell. If <command> is specified, the shell runs after
+ the command finishes executing
+EOF
+}
+
+download()
+{
+ local file="$1"
+
+ echo "Downloading $file..." >&2
+ curl -Lsf "$file" -o "${@:2}"
+}
+
+recompile_kernel()
+{
+ local kernel_checkout="$1"
+ local make_command="$2"
+
+ cd "${kernel_checkout}"
+
+ ${make_command} olddefconfig
+ ${make_command} headers
+ ${make_command}
+}
+
+update_selftests()
+{
+ local kernel_checkout="$1"
+ local selftests_dir="${kernel_checkout}/tools/testing/selftests/hid"
+
+ cd "${selftests_dir}"
+ ${make_command}
+}
+
+run_vm()
+{
+ local run_dir="$1"
+ local b2c="$2"
+ local kernel_bzimage="$3"
+ local command="$4"
+ local post_command=""
+
+ cd "${run_dir}"
+
+ if ! which "${QEMU_BINARY}" &> /dev/null; then
+ cat <<EOF
+Could not find ${QEMU_BINARY}
+Please install qemu or set the QEMU_BINARY environment variable.
+EOF
+ exit 1
+ fi
+
+ # alpine (used in post-container requires the PATH to have /bin
+ export PATH=$PATH:/bin
+
+ if [[ "${debug_shell}" != "yes" ]]
+ then
+ touch ${OUTPUT_DIR}/${LOG_FILE}
+ command="mount bpffs -t bpf /sys/fs/bpf/; set -o pipefail ; ${command} 2>&1 | tee ${OUTPUT_DIR}/${LOG_FILE}"
+ post_command="cat ${OUTPUT_DIR}/${LOG_FILE}"
+ else
+ command="mount bpffs -t bpf /sys/fs/bpf/; ${command}"
+ fi
+
+ set +e
+ $b2c --command "${command}" \
+ --kernel ${kernel_bzimage} \
+ --workdir ${OUTPUT_DIR} \
+ --image ${CONTAINER_IMAGE}
+
+ echo $? > ${OUTPUT_DIR}/${EXIT_STATUS_FILE}
+
+ set -e
+
+ ${post_command}
+}
+
+is_rel_path()
+{
+ local path="$1"
+
+ [[ ${path:0:1} != "/" ]]
+}
+
+do_update_kconfig()
+{
+ local kernel_checkout="$1"
+ local kconfig_file="$2"
+
+ rm -f "$kconfig_file" 2> /dev/null
+
+ for config in "${KCONFIG_REL_PATHS[@]}"; do
+ local kconfig_src="${config}"
+ cat "$kconfig_src" >> "$kconfig_file"
+ done
+}
+
+update_kconfig()
+{
+ local kernel_checkout="$1"
+ local kconfig_file="$2"
+
+ if [[ -f "${kconfig_file}" ]]; then
+ local local_modified="$(stat -c %Y "${kconfig_file}")"
+
+ for config in "${KCONFIG_REL_PATHS[@]}"; do
+ local kconfig_src="${config}"
+ local src_modified="$(stat -c %Y "${kconfig_src}")"
+ # Only update the config if it has been updated after the
+ # previously cached config was created. This avoids
+ # unnecessarily compiling the kernel and selftests.
+ if [[ "${src_modified}" -gt "${local_modified}" ]]; then
+ do_update_kconfig "$kernel_checkout" "$kconfig_file"
+ # Once we have found one outdated configuration
+ # there is no need to check other ones.
+ break
+ fi
+ done
+ else
+ do_update_kconfig "$kernel_checkout" "$kconfig_file"
+ fi
+}
+
+main()
+{
+ local script_dir="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
+ local kernel_checkout=$(realpath "${script_dir}"/../../../../)
+ # By default the script searches for the kernel in the checkout directory but
+ # it also obeys environment variables O= and KBUILD_OUTPUT=
+ local kernel_bzimage="${kernel_checkout}/${BZIMAGE}"
+ local command="${DEFAULT_COMMAND}"
+ local update_b2c="no"
+ local debug_shell="no"
+ local build_only="no"
+
+ while getopts ':hsud:j:b' opt; do
+ case ${opt} in
+ u)
+ update_b2c="yes"
+ ;;
+ d)
+ OUTPUT_DIR="$OPTARG"
+ ;;
+ j)
+ NUM_COMPILE_JOBS="$OPTARG"
+ ;;
+ s)
+ command="/bin/sh"
+ debug_shell="yes"
+ ;;
+ b)
+ build_only="yes"
+ ;;
+ h)
+ usage
+ exit 0
+ ;;
+ \? )
+ echo "Invalid Option: -$OPTARG"
+ usage
+ exit 1
+ ;;
+ : )
+ echo "Invalid Option: -$OPTARG requires an argument"
+ usage
+ exit 1
+ ;;
+ esac
+ done
+ shift $((OPTIND -1))
+
+ # trap 'catch "$?"' EXIT
+ if [[ "${build_only}" == "no" && "${debug_shell}" == "no" ]]; then
+ if [[ $# -eq 0 ]]; then
+ echo "No command specified, will run ${DEFAULT_COMMAND} in the vm"
+ else
+ command="$@"
+
+ if [[ "${command}" == "/bin/bash" || "${command}" == "bash" ]]
+ then
+ debug_shell="yes"
+ fi
+ fi
+ fi
+
+ local kconfig_file="${OUTPUT_DIR}/latest.config"
+ local make_command="make -j ${NUM_COMPILE_JOBS} KCONFIG_CONFIG=${kconfig_file}"
+
+ # Figure out where the kernel is being built.
+ # O takes precedence over KBUILD_OUTPUT.
+ if [[ "${O:=""}" != "" ]]; then
+ if is_rel_path "${O}"; then
+ O="$(realpath "${PWD}/${O}")"
+ fi
+ kernel_bzimage="${O}/${BZIMAGE}"
+ make_command="${make_command} O=${O}"
+ elif [[ "${KBUILD_OUTPUT:=""}" != "" ]]; then
+ if is_rel_path "${KBUILD_OUTPUT}"; then
+ KBUILD_OUTPUT="$(realpath "${PWD}/${KBUILD_OUTPUT}")"
+ fi
+ kernel_bzimage="${KBUILD_OUTPUT}/${BZIMAGE}"
+ make_command="${make_command} KBUILD_OUTPUT=${KBUILD_OUTPUT}"
+ fi
+
+ local b2c="${OUTPUT_DIR}/vm2c.py"
+
+ echo "Output directory: ${OUTPUT_DIR}"
+
+ mkdir -p "${OUTPUT_DIR}"
+ update_kconfig "${kernel_checkout}" "${kconfig_file}"
+
+ recompile_kernel "${kernel_checkout}" "${make_command}"
+ update_selftests "${kernel_checkout}" "${make_command}"
+
+ if [[ "${build_only}" == "no" ]]; then
+ if [[ "${update_b2c}" == "no" && ! -f "${b2c}" ]]; then
+ echo "vm2c script not found in ${b2c}"
+ update_b2c="yes"
+ fi
+
+ if [[ "${update_b2c}" == "yes" ]]; then
+ download $B2C_URL $b2c
+ chmod +x $b2c
+ fi
+
+ run_vm "${kernel_checkout}" $b2c "${kernel_bzimage}" "${command}"
+ if [[ "${debug_shell}" != "yes" ]]; then
+ echo "Logs saved in ${OUTPUT_DIR}/${LOG_FILE}"
+ fi
+
+ exit $(cat ${OUTPUT_DIR}/${EXIT_STATUS_FILE})
+ fi
+}
+
+main "$@"