diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-09-13 12:27:00 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-09-13 12:27:00 -0700 |
| commit | 22098763a10d9c1340827fcf6edab66f153b27f0 (patch) | |
| tree | 3962f95a34df034652ff93686cdd156c7197f2d9 /tools/testing/selftests/hid | |
| download | linux-stable-22098763a10d9c1340827fcf6edab66f153b27f0.tar.gz linux-stable-22098763a10d9c1340827fcf6edab66f153b27f0.zip | |
Merge tag 'trace-v7.3-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-tracegrafted
Pull tracing fixes from Steven Rostedt:
- Don't destroy user event fields when removal fails
User event fields are destroyed before the event is removed from
visibility. But that can fail leaving the still visible event with no
fields. Move the destroying of the fields to after the event is
successfully removed from visibility.
- Initialize function graph state is fork before calling
copy_exec_state()
For non-CLONE_VM forks, copy_exec_state() allocates a new
task_exec_state. If that allocation fails, ftrace_graph_exit_task()
will free the tasks ret_stack pointer. Since that pointer is still
using the parent's ret_stack, it mistakenly frees the parent's
pointer too.
Call ftrace_graph_init() on the task first which will NULL out the
new tasks's ret_stack and if the copy fails, it will not free
anything.
- Remove FGRAPH_MAX_INDEX
The macro FGRAPH_MAX_INDEX was added but never used. Remove it.
- Save ent_size in function graph printing of nested functions
The function graph tracer needs to look at the next event to see if
the next event is the return of the current function entry. If it is,
it prints a single line:
ktime_get();
Otherwise it prints it like a nested function:
tick_nohz_irq_exit() {
ktime_get();
kcpustat_irq_exit();
}
In order to look at the next event, it must save the current event so
that it has the information to print from it. It saves the event in
the iterator descriptor called "ent". What it doesn't save is the
ent_size of the event which is now used to know if the function graph
arguments are to be printed. The peek doesn't save the size so the
size used happens to be that of the size of the last event that was
seen.
Save the entry event size in the iterator descriptor so that the
correct size is used.
- Fix several errors with freeing data in the histogram code
The histogram code had a lot of leaked or or incorrect accounting
when failures happen. Correct them.
- Fix histogram regression of .percent and .graph modifiers
Up until 6.3 histogram values could have "percent" or "graph"
modifiers that changed how they were printed. But a change that added
restricting histograms values from being strings, stack traces and
other modifiers inadvertently prevented them from using the percent
and graph modifiers, which were legal use cases for values.
Put back the percent and graph modifiers.
- Fix various typos in the comments
- Set the trace_clock before initializing a histogram with clock
argument
The histogram API allows the user to specific which trace clock to
use via a "clock=" string. The histogram is set up first before the
clock is checked. If the passed in clock is not valid, it exits
without fully fixing up the histogram leaving it on the list and a
use-after-free can trigger.
Update the clock argument first and if it fails then exit gracefully
before the histogram trigger is placed on any lists.
- Restore :mod: trailer after parsing in ftrace_set_clr_event
The function ftrace_set_clr_event() modifies the parse string and
needs to put it back to what was passed in. It searches for ":mod:"
via a strsep() but fails to put back the first ':' in the string.
Add back the ':' in the passed in string.
- Take trace_array reference when opening a tracer options file
The options files are dynamically created and some tracers add their
own options. When a tracer adds their own list of options, the
trace_array holding them has an array to hold the list of options for
each tracer. This array increases in size via a krealloc(), and the
new entry gets a newly allocated array to hold the options of the new
tracer being added.
The element in each entry of the tracer's option array holds a
pointer back to the trace_array, a pointer to the tracer it is
associated to, a pointer to the flags of the option.
The issue is that these arrays are freed when the trace_array is
freed when its instance it represents is removed from the instances
directory. There's a race that an open of one of these options files
can happen when the instance is being removed.
Add a new helper function to be called by the open function of the
options file to iterate all existing trace_arrays under a lock and
find the one that has the given option element in one of it's tracer
arrays. If found, then update the associated trace_array's reference
counter to keep it from being freed. If not found, have the open call
return -ENODEV.
- Disable interrupts when acquiring the lock in rb_wake_up_waiters()
The function rb_wake_up_waiters() assumes it will be called in
interrupt context and does not disable irqs when taking
cpu_buffer->reader_lock, which can be called in hard interrupt
context. The issue is in PREEMPT_RT, this function is called in
thread context leaving this lock open to a deadlock.
Take the lock with interrupts disabled.
- Use rcu_assign_pointer() for tmp_ops filter hash
The tmp_ops used in update_ftrace_direct_mod() assigns its
filter_hash field directly, but that field is annotated as __rcu and
sparse complains. Assign it with rcu_assign_pointer()
- Fix use-after-free in enable_trigger_private_data_free()
The trace_event_call is accessed through the event_trigger_data's
trace_event_file pointer to put the trace_event_call on freeing. The
issue is that the trace_event_file data may have been freed already
causing a use-after-free. Add a field to the event_trigger_data that
points directly to the trace_event_call so that it can decrement its
reference directly without needing to go through the
trace_event_file.
- Fix accounting of buffer data remote headers
trace_buffer_desc_size() and trace_remote_alloc_buffer() undercount
the number of pages is needed for the asked for size as it doesn't
take into account the meta data on each page. Add a helper function
to do the calculation properly and use that in these functions.
- Catch nr_page_va overflow in ring_buffer_desc sizing
The number of pages per remote ring buffer is capped by
ring_buffer_desc::nr_page_va (32 bits). A buffer_size large enough to
overflow that field would silently allocate a descriptor smaller than
what was asked for.
- Do not resize the subbuf order if any per_cpu buffer is disabled
The mmapping of ring buffers disables resizing the subbuffers, but it
is done per-cpu whereas the subbuf size change is done for all the
per_cpu buffers under the buffer->mutex. It could change the size of
some while the mapping is happening on others. Have the resize of the
subbuf order check all the per_cpu buffers under the lock to see if
any of them is disabled before starting and causing an inconsistency
between buffers that are being mapped.
* tag 'trace-v7.3-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: (25 commits)
ring-buffer: Check resize_disabled before publishing the new subbuf order
tracing/remotes: Catch nr_page_va overflow in ring_buffer_desc sizing
tracing/remotes: Account for ring buffer page header in size calculation
tracing: Don't dereference trace_event_file in deferred trigger free
ftrace: Use rcu_assign_pointer() for tmp_ops filter hash
ring-buffer: Acquire the lock with irqsave in rb_wake_up_waiters()
tracing: Take trace_array reference when opening a tracer options file
tracing: Fix ring_buffer_read_page_size() kernel-doc
tracing: Restore :mod: trailer after parsing in ftrace_set_clr_event()
tracing: Fix memory corruption from a "STACKTRACE" histogram key
tracing: Fix memory corruption from the histogram stacktrace modifier
tracing: Undo the registration when enabling the histogram trigger fails
tracing: Take the reference before publishing the named histogram trigger
tracing: Set the trace clock before registering the histogram trigger
tracing: Fix typo "preceeded" in comment
tracing: Fix typo "availabe" in comment
tracing: Let histogram values keep the percent and graph modifiers
tracing: Keep the entry count when the histogram stats allocation fails
tracing: Free histogram the field rejected for a bad modifier
tracing: Free histogram the var ref when its initialization fails
...
Diffstat (limited to 'tools/testing/selftests/hid')
41 files changed, 15290 insertions, 0 deletions
diff --git a/tools/testing/selftests/hid/.gitignore b/tools/testing/selftests/hid/.gitignore new file mode 100644 index 000000000..933f48381 --- /dev/null +++ b/tools/testing/selftests/hid/.gitignore @@ -0,0 +1,7 @@ +bpftool +*.skel.h +/host-tools +/tools +hid_bpf +hidraw +results diff --git a/tools/testing/selftests/hid/Makefile b/tools/testing/selftests/hid/Makefile new file mode 100644 index 000000000..2f423de83 --- /dev/null +++ b/tools/testing/selftests/hid/Makefile @@ -0,0 +1,237 @@ +# 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 + +TEST_FILES := run-hid-tools-tests.sh +TEST_FILES += tests + +CXX ?= $(CROSS_COMPILE)g++ + +HOSTPKG_CONFIG := pkg-config + +CFLAGS += -g -O0 -rdynamic -Wall -Werror -I$(OUTPUT) $(KHDR_INCLUDES) +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 hidraw + +# 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) +msg = +else +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 $@ + +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) \ + -Wno-microsoft-anon-tag \ + -fms-extensions \ + -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) hid_common.h + $(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 000000000..1758b055f --- /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 000000000..38c51158a --- /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_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_XTABLES_LEGACY=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 000000000..a8721f403 --- /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 000000000..656f2d5ae --- /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 000000000..5bbabc12c --- /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 000000000..1ba00c0ca --- /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 000000000..52c5ccf42 --- /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 000000000..55368f17d --- /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 000000000..7b4ad4f64 --- /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 000000000..d03a1ddbf --- /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 000000000..c863c4426 --- /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 000000000..e86b3feda --- /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 000000000..3f0debe7e --- /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 000000000..1630c2272 --- /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 000000000..7ab86296f --- /dev/null +++ b/tools/testing/selftests/hid/hid_bpf.c @@ -0,0 +1,968 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2022-2024 Red Hat */ +#include "hid.skel.h" +#include "hid_common.h" +#include <bpf/bpf.h> + +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; +}; + +FIXTURE(hid_bpf) { + struct uhid_device hid; + int hidraw_fd; + struct hid *skel; + struct bpf_link *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; + + if (!self->skel) + return; + + hid__detach(self->skel); + + for (i = 0; i < ARRAY_SIZE(self->hid_links); i++) { + if (self->hid_links[i]) + bpf_link__destroy(self->hid_links[i]); + } + + hid__destroy(self->skel); + self->skel = NULL; +} + +FIXTURE_TEARDOWN(hid_bpf) { + void *uhid_err; + + uhid_destroy(_metadata, &self->hid); + + detach_bpf(self); + pthread_join(self->hid.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) +{ + int err; + + err = setup_uhid(_metadata, &self->hid, BUS_USB, 0x0001, 0x0a36, rdesc, sizeof(rdesc)); + ASSERT_OK(err); +} + +struct test_program { + const char *name; + int insert_head; +}; +#define LOAD_PROGRAMS(progs) \ + load_programs(progs, ARRAY_SIZE(progs), false, _metadata, self, variant) +#define LOAD_PROGRAMS_MAY_FAIL(progs) \ + load_programs(progs, ARRAY_SIZE(progs), true, _metadata, self, variant) +#define LOAD_BPF \ + load_programs(NULL, 0, false, _metadata, self, variant) +static int load_programs(const struct test_program programs[], + const size_t progs_count, + bool load_may_fail, + struct __test_metadata *_metadata, + FIXTURE_DATA(hid_bpf) * self, + const FIXTURE_VARIANT(hid_bpf) * variant) +{ + struct bpf_map *iter_map; + int err = -EINVAL; + + 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"); + + /* + * Disable all struct_ops maps by default so libbpf does not autoload + * programs referenced by maps that are unrelated to the current test. + */ + bpf_object__for_each_map(iter_map, *self->skel->skeleton->obj) { + if (bpf_map__type(iter_map) == BPF_MAP_TYPE_STRUCT_OPS) { + err = bpf_map__set_autocreate(iter_map, false); + ASSERT_OK(err) TH_LOG("can not disable struct_ops map '%s'", + bpf_map__name(iter_map)); + } + + bpf_map__set_autoattach(iter_map, false); + } + + for (int i = 0; i < progs_count; i++) { + struct bpf_program *prog; + struct bpf_map *map; + int *ops_hid_id; + + 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); + + map = bpf_object__find_map_by_name(*self->skel->skeleton->obj, + programs[i].name + 4); + ASSERT_OK_PTR(map) TH_LOG("can not find struct_ops by name '%s'", + programs[i].name + 4); + + err = bpf_map__set_autocreate(map, true); + ASSERT_OK(err) TH_LOG("can not enable struct_ops map '%s'", + programs[i].name + 4); + + /* hid_id is the first field of struct hid_bpf_ops */ + ops_hid_id = bpf_map__initial_value(map, NULL); + ASSERT_OK_PTR(ops_hid_id) TH_LOG("unable to retrieve struct_ops data"); + + *ops_hid_id = self->hid.hid_id; + } + + err = hid__load(self->skel); + if (err && load_may_fail) + return err; + + ASSERT_OK(err) TH_LOG("hid_skel_load failed: %d", err); + + for (int i = 0; i < progs_count; i++) { + struct bpf_map *map; + + map = bpf_object__find_map_by_name(*self->skel->skeleton->obj, + programs[i].name + 4); + ASSERT_OK_PTR(map) TH_LOG("can not find struct_ops by name '%s'", + programs[i].name + 4); + + self->hid_links[i] = bpf_map__attach_struct_ops(map); + ASSERT_OK_PTR(self->hid_links[i]) TH_LOG("failed to attach struct ops '%s'", + programs[i].name + 4); + } + + hid__attach(self->skel); + + self->hidraw_fd = open_hidraw(&self->hid); + ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw"); + return 0; +} + +/* + * 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->hid, 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->hid, 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); +} + +/* + * 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, subprog_raw_event) +{ + const struct test_program progs[] = { + { .name = "hid_subprog_first_event" }, + }; + __u8 buf[10] = {0}; + int err; + + LOAD_PROGRAMS(progs); + + /* inject one event */ + buf[0] = 1; + buf[1] = 42; + uhid_send_event(_metadata, &self->hid, 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); + + /* inject another event */ + memset(buf, 0, sizeof(buf)); + buf[0] = 1; + buf[1] = 47; + uhid_send_event(_metadata, &self->hid, 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[2], 52); +} + +/* + * Attach hid_first_event to the given uhid device, + * attempt at re-attaching it, we should not lock and + * return an invalid struct bpf_link + */ +TEST_F(hid_bpf, multiple_attach) +{ + const struct test_program progs[] = { + { .name = "hid_first_event" }, + }; + struct bpf_link *link; + + LOAD_PROGRAMS(progs); + + link = bpf_map__attach_struct_ops(self->skel->maps.first_event); + ASSERT_NULL(link) TH_LOG("unexpected return value when re-attaching the struct_ops"); +} + +/* + * 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" }, + }; + struct bpf_link *link; + __u8 buf[10] = {0}; + int err, link_fd; + + LOAD_PROGRAMS(progs); + + link = self->hid_links[0]; + ASSERT_OK_PTR(link) TH_LOG("HID-BPF link not created"); + + link_fd = bpf_link__fd(link); + ASSERT_GE(link_fd, 0) TH_LOG("HID-BPF link FD not valid"); + + /* inject one event */ + buf[0] = 1; + buf[1] = 42; + uhid_send_event(_metadata, &self->hid, 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_fd, 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->hid); + 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->hid, 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->hid, 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->hid, 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.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.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.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); +} + +/* + * Call hid_hw_raw_request against the given uhid device, + * check that the program is called and prevents the + * call to uhid. + */ +TEST_F(hid_bpf, test_hid_filter_raw_request_call) +{ + const struct test_program progs[] = { + { .name = "hid_test_filter_raw_request" }, + }; + __u8 buf[10] = {0}; + int err; + + LOAD_PROGRAMS(progs); + + /* first check that we did not attach to device_event */ + + /* inject one event */ + buf[0] = 1; + buf[1] = 42; + uhid_send_event(_metadata, &self->hid, 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[1], 42); + ASSERT_EQ(buf[2], 0) TH_LOG("leftovers_from_previous_test"); + + /* now check that our program is preventing hid_hw_raw_request() */ + + /* emit hid_hw_raw_request from hidraw */ + /* Get Feature */ + memset(buf, 0, sizeof(buf)); + buf[0] = 0x1; /* Report Number */ + err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf); + ASSERT_LT(err, 0) TH_LOG("unexpected success while reading HIDIOCGFEATURE: %d", err); + ASSERT_EQ(errno, 20) TH_LOG("unexpected error code while reading HIDIOCGFEATURE: %d", + errno); + + /* remove our bpf program and check that we can now emit commands */ + + /* detach the program */ + detach_bpf(self); + + self->hidraw_fd = open_hidraw(&self->hid); + ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw"); + + err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf); + ASSERT_GE(err, 0) TH_LOG("error while reading HIDIOCGFEATURE: %d", err); +} + +/* + * Call hid_hw_raw_request against the given uhid device, + * check that the program is called and can issue the call + * to uhid and transform the answer. + */ +TEST_F(hid_bpf, test_hid_change_raw_request_call) +{ + const struct test_program progs[] = { + { .name = "hid_test_hidraw_raw_request" }, + }; + __u8 buf[10] = {0}; + int err; + + LOAD_PROGRAMS(progs); + + /* emit hid_hw_raw_request from hidraw */ + /* Get Feature */ + memset(buf, 0, sizeof(buf)); + buf[0] = 0x1; /* Report Number */ + err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf); + ASSERT_EQ(err, 3) TH_LOG("unexpected returned size while reading HIDIOCGFEATURE: %d", err); + + ASSERT_EQ(buf[0], 2); + ASSERT_EQ(buf[1], 3); + ASSERT_EQ(buf[2], 4); +} + +/* + * Call hid_hw_raw_request against the given uhid device, + * check that the program is not making infinite loops. + */ +TEST_F(hid_bpf, test_hid_infinite_loop_raw_request_call) +{ + const struct test_program progs[] = { + { .name = "hid_test_infinite_loop_raw_request" }, + }; + __u8 buf[10] = {0}; + int err; + + LOAD_PROGRAMS(progs); + + /* emit hid_hw_raw_request from hidraw */ + /* Get Feature */ + memset(buf, 0, sizeof(buf)); + buf[0] = 0x1; /* Report Number */ + err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf); + ASSERT_EQ(err, 3) TH_LOG("unexpected returned size while reading HIDIOCGFEATURE: %d", err); +} + +/* + * Call hid_hw_output_report against the given uhid device, + * check that the program is called and prevents the + * call to uhid. + */ +TEST_F(hid_bpf, test_hid_filter_output_report_call) +{ + const struct test_program progs[] = { + { .name = "hid_test_filter_output_report" }, + }; + __u8 buf[10] = {0}; + int err; + + LOAD_PROGRAMS(progs); + + /* first check that we did not attach to device_event */ + + /* inject one event */ + buf[0] = 1; + buf[1] = 42; + uhid_send_event(_metadata, &self->hid, 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[1], 42); + ASSERT_EQ(buf[2], 0) TH_LOG("leftovers_from_previous_test"); + + /* now check that our program is preventing hid_hw_output_report() */ + + buf[0] = 1; /* report ID */ + buf[1] = 2; + buf[2] = 42; + + err = write(self->hidraw_fd, buf, 3); + ASSERT_LT(err, 0) TH_LOG("unexpected success while sending hid_hw_output_report: %d", err); + ASSERT_EQ(errno, 25) TH_LOG("unexpected error code while sending hid_hw_output_report: %d", + errno); + + /* remove our bpf program and check that we can now emit commands */ + + /* detach the program */ + detach_bpf(self); + + self->hidraw_fd = open_hidraw(&self->hid); + ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw"); + + err = write(self->hidraw_fd, buf, 3); + ASSERT_GE(err, 0) TH_LOG("error while sending hid_hw_output_report: %d", err); +} + +/* + * Call hid_hw_output_report against the given uhid device, + * check that the program is called and can issue the call + * to uhid and transform the answer. + */ +TEST_F(hid_bpf, test_hid_change_output_report_call) +{ + const struct test_program progs[] = { + { .name = "hid_test_hidraw_output_report" }, + }; + __u8 buf[10] = {0}; + int err; + + LOAD_PROGRAMS(progs); + + /* emit hid_hw_output_report from hidraw */ + buf[0] = 1; /* report ID */ + buf[1] = 2; + buf[2] = 42; + + err = write(self->hidraw_fd, buf, 10); + ASSERT_EQ(err, 2) TH_LOG("unexpected returned size while sending hid_hw_output_report: %d", + err); +} + +/* + * Call hid_hw_output_report against the given uhid device, + * check that the program is not making infinite loops. + */ +TEST_F(hid_bpf, test_hid_infinite_loop_output_report_call) +{ + const struct test_program progs[] = { + { .name = "hid_test_infinite_loop_output_report" }, + }; + __u8 buf[10] = {0}; + int err; + + LOAD_PROGRAMS(progs); + + /* emit hid_hw_output_report from hidraw */ + buf[0] = 1; /* report ID */ + buf[1] = 2; + buf[2] = 42; + + err = write(self->hidraw_fd, buf, 8); + ASSERT_EQ(err, 2) TH_LOG("unexpected returned size while sending hid_hw_output_report: %d", + err); +} + +/* + * Attach hid_multiply_event_wq 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 add extra data + */ +TEST_F(hid_bpf, test_multiply_events_wq) +{ + const struct test_program progs[] = { + { .name = "hid_test_multiply_events_wq" }, + }; + __u8 buf[10] = {0}; + int err; + + LOAD_PROGRAMS(progs); + + /* inject one event */ + buf[0] = 1; + buf[1] = 42; + uhid_send_event(_metadata, &self->hid, 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[1], 47); + + usleep(100000); + + /* 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], 3); +} + +/* + * Attach hid_multiply_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 add extra data + */ +TEST_F(hid_bpf, test_multiply_events) +{ + const struct test_program progs[] = { + { .name = "hid_test_multiply_events" }, + }; + __u8 buf[10] = {0}; + int err; + + LOAD_PROGRAMS(progs); + + /* inject one event */ + buf[0] = 1; + buf[1] = 42; + uhid_send_event(_metadata, &self->hid, 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], 47); + + /* 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], 52); +} + +/* + * Call hid_bpf_input_report against the given uhid device, + * check that the program is not making infinite loops. + */ +TEST_F(hid_bpf, test_hid_infinite_loop_input_report_call) +{ + const struct test_program progs[] = { + { .name = "hid_test_infinite_loop_input_report" }, + }; + __u8 buf[10] = {0}; + int err; + + LOAD_PROGRAMS(progs); + + /* emit hid_hw_output_report from hidraw */ + buf[0] = 1; /* report ID */ + buf[1] = 2; + buf[2] = 42; + + uhid_send_event(_metadata, &self->hid, 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[1], 3); + + /* read the data from hidraw: hid_bpf_try_input_report should work exactly one time */ + 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], 4); + + /* read the data from hidraw: there should be none */ + memset(buf, 0, sizeof(buf)); + err = read(self->hidraw_fd, buf, sizeof(buf)); + ASSERT_EQ(err, -1) TH_LOG("read_hidraw"); +} + +/* + * 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->hid, 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); +} + +TEST_F(hid_bpf, test_rdesc_fixup_get_data_overflow) +{ + const struct test_program progs[] = { + { .name = "hid_rdesc_fixup_get_data_overflow" }, + }; + + /* newer verifier can detect the overflow at load time */ + if (LOAD_PROGRAMS_MAY_FAIL(progs)) + return; + + ASSERT_EQ(self->skel->bss->get_data_overflow_check, 1); +} + +TEST_F(hid_bpf, test_rdesc_fixup_change_uniq_name_phys) +{ + const struct test_program progs[] = { + { .name = "hid_rdesc_fixup_change_uniq_name_phys" }, + }; + char expected[256], buf[256] = {}; + int err; + + LOAD_PROGRAMS(progs); + + err = ioctl(self->hidraw_fd, HIDIOCGRAWNAME(sizeof(buf)), buf); + ASSERT_GE(err, 0) TH_LOG("HIDIOCGRAWNAME"); + ASSERT_STREQ("name coming from bpf", buf); + + snprintf(expected, sizeof(expected), "%d phys:coming:from:bpf", self->hid.dev_id); + + err = ioctl(self->hidraw_fd, HIDIOCGRAWPHYS(sizeof(buf)), buf); + ASSERT_GE(err, 0) TH_LOG("HIDIOCGRAWPHYS"); + ASSERT_STREQ(expected, buf); + + err = ioctl(self->hidraw_fd, HIDIOCGRAWUNIQ(sizeof(buf)), buf); + ASSERT_GE(err, 0) TH_LOG("HIDIOCGRAWUNIQ"); + ASSERT_STREQ("uniq:coming:from:bpf", buf); + +} + +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; +} + +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/hid_common.h b/tools/testing/selftests/hid/hid_common.h new file mode 100644 index 000000000..e3b267446 --- /dev/null +++ b/tools/testing/selftests/hid/hid_common.h @@ -0,0 +1,480 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2022-2024 Red Hat */ + +#include "kselftest_harness.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; }) + +struct uhid_device { + int dev_id; /* uniq (random) number to identify the device */ + int uhid_fd; + int hid_id; /* HID device id in the system */ + __u16 bus; + __u32 vid; + __u32 pid; + pthread_t tid; /* thread for reading uhid events */ +}; + +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 }; + +#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, + __u16 bus, __u32 vid, __u32 pid, __u8 *rdesc, + size_t rdesc_size) +{ + 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 = rdesc_size; + ev.u.create.bus = bus; + ev.u.create.vendor = vid; + ev.u.create.product = pid; + 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, struct uhid_device *hid) +{ + struct uhid_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_DESTROY; + + uhid_write(_metadata, hid->uhid_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"); + + answer.type = UHID_SET_REPORT_REPLY; + answer.u.set_report_reply.id = ev.u.set_report.id; + answer.u.set_report_reply.err = 0; /* success */ + + uhid_write(_metadata, fd, &answer); + 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, struct uhid_device *hid, + __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, hid->uhid_fd, &ev); +} + +static bool match_sysfs_device(struct uhid_device *hid, const char *workdir, struct dirent *dir) +{ + char target[20] = ""; + char phys[512]; + char uevent[1024]; + char temp[512]; + int fd, nread; + bool found = false; + + snprintf(target, sizeof(target), "%04X:%04X:%04X.*", hid->bus, hid->vid, hid->pid); + + 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", hid->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(struct uhid_device *hid) +{ + 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(hid, workdir, dir)) + continue; + + str_id = dir->d_name + sizeof("0000:0000:0000."); + found = (int)strtol(str_id, NULL, 16); + + break; + } + closedir(d); + } + if (found < 0) + usleep(100000); + } + + return found; +} + +static int get_hidraw(struct uhid_device *hid) +{ + 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(hid, 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(struct uhid_device *hid) +{ + int hidraw_number; + char hidraw_path[64] = { 0 }; + + hidraw_number = get_hidraw(hid); + 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); +} + +static int setup_uhid(struct __test_metadata *_metadata, struct uhid_device *hid, + __u16 bus, __u32 vid, __u32 pid, const __u8 *rdesc, size_t rdesc_size) +{ + const char *path = "/dev/uhid"; + time_t t; + int ret; + + /* initialize random number generator */ + srand((unsigned int)time(&t)); + + hid->dev_id = rand() % 1024; + hid->bus = bus; + hid->vid = vid; + hid->pid = pid; + + hid->uhid_fd = open(path, O_RDWR | O_CLOEXEC); + ASSERT_GE(hid->uhid_fd, 0) TH_LOG("open uhid-cdev failed; %d", hid->uhid_fd); + + ret = uhid_create(_metadata, hid->uhid_fd, hid->dev_id, bus, vid, pid, + (__u8 *)rdesc, rdesc_size); + ASSERT_EQ(0, ret) { + TH_LOG("create uhid device failed: %d", ret); + close(hid->uhid_fd); + return ret; + } + + /* locate the uevent file of the created device */ + hid->hid_id = get_hid_id(hid); + ASSERT_GT(hid->hid_id, 0) + TH_LOG("Could not locate uhid device id: %d", hid->hid_id); + + ret = uhid_start_listener(_metadata, &hid->tid, hid->uhid_fd); + ASSERT_EQ(0, ret) { + TH_LOG("could not start udev listener: %d", ret); + close(hid->uhid_fd); + return ret; + } + + return 0; +} diff --git a/tools/testing/selftests/hid/hidraw.c b/tools/testing/selftests/hid/hidraw.c new file mode 100644 index 000000000..d625772f8 --- /dev/null +++ b/tools/testing/selftests/hid/hidraw.c @@ -0,0 +1,694 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2022-2024 Red Hat */ + +#include "hid_common.h" +#include <linux/input.h> +#include <string.h> +#include <sys/ioctl.h> + +/* for older kernels */ +#ifndef HIDIOCREVOKE +#define HIDIOCREVOKE _IOW('H', 0x0D, int) /* Revoke device access */ +#endif /* HIDIOCREVOKE */ + +FIXTURE(hidraw) { + struct uhid_device hid; + int hidraw_fd; +}; +static void close_hidraw(FIXTURE_DATA(hidraw) * self) +{ + if (self->hidraw_fd) + close(self->hidraw_fd); + self->hidraw_fd = 0; +} + +FIXTURE_TEARDOWN(hidraw) { + void *uhid_err; + + uhid_destroy(_metadata, &self->hid); + + close_hidraw(self); + pthread_join(self->hid.tid, &uhid_err); +} +#define TEARDOWN_LOG(fmt, ...) do { \ + TH_LOG(fmt, ##__VA_ARGS__); \ + hidraw_teardown(_metadata, self, variant); \ +} while (0) + +FIXTURE_SETUP(hidraw) +{ + int err; + + err = setup_uhid(_metadata, &self->hid, BUS_USB, 0x0001, 0x0a37, rdesc, sizeof(rdesc)); + ASSERT_OK(err); + + self->hidraw_fd = open_hidraw(&self->hid); + 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(hidraw, test_create_uhid) +{ +} + +/* + * Inject one event in the uhid device, + * check that we get the same data through hidraw + */ +TEST_F(hidraw, raw_event) +{ + __u8 buf[10] = {0}; + int err; + + /* inject one event */ + buf[0] = 1; + buf[1] = 42; + uhid_send_event(_metadata, &self->hid, 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[1], 42); +} + +/* + * After initial opening/checks of hidraw, revoke the hidraw + * node and check that we can not read any more data. + */ +TEST_F(hidraw, raw_event_revoked) +{ + __u8 buf[10] = {0}; + int err; + + /* inject one event */ + buf[0] = 1; + buf[1] = 42; + uhid_send_event(_metadata, &self->hid, 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[1], 42); + + /* call the revoke ioctl */ + err = ioctl(self->hidraw_fd, HIDIOCREVOKE, NULL); + ASSERT_OK(err) TH_LOG("couldn't revoke the hidraw fd"); + + /* inject one other event */ + buf[0] = 1; + buf[1] = 43; + uhid_send_event(_metadata, &self->hid, buf, 6); + + /* read the data from hidraw */ + memset(buf, 0, sizeof(buf)); + err = read(self->hidraw_fd, buf, sizeof(buf)); + ASSERT_EQ(err, -1) TH_LOG("read_hidraw"); + ASSERT_EQ(errno, ENODEV) TH_LOG("unexpected error code while reading the hidraw node: %d", + errno); +} + +/* + * Revoke the hidraw node and check that we can not do any ioctl. + */ +TEST_F(hidraw, ioctl_revoked) +{ + int err, desc_size = 0; + + /* call the revoke ioctl */ + err = ioctl(self->hidraw_fd, HIDIOCREVOKE, NULL); + ASSERT_OK(err) TH_LOG("couldn't revoke the hidraw fd"); + + /* do an ioctl */ + err = ioctl(self->hidraw_fd, HIDIOCGRDESCSIZE, &desc_size); + ASSERT_EQ(err, -1) TH_LOG("ioctl_hidraw"); + ASSERT_EQ(errno, ENODEV) TH_LOG("unexpected error code while doing an ioctl: %d", + errno); +} + +/* + * Setup polling of the fd, and check that revoke works properly. + */ +TEST_F(hidraw, poll_revoked) +{ + struct pollfd pfds[1]; + __u8 buf[10] = {0}; + int err, ready; + + /* setup polling */ + pfds[0].fd = self->hidraw_fd; + pfds[0].events = POLLIN; + + /* inject one event */ + buf[0] = 1; + buf[1] = 42; + uhid_send_event(_metadata, &self->hid, buf, 6); + + while (true) { + ready = poll(pfds, 1, 5000); + ASSERT_EQ(ready, 1) TH_LOG("poll return value"); + + if (pfds[0].revents & POLLIN) { + 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], 42); + + /* call the revoke ioctl */ + err = ioctl(self->hidraw_fd, HIDIOCREVOKE, NULL); + ASSERT_OK(err) TH_LOG("couldn't revoke the hidraw fd"); + } else { + break; + } + } + + ASSERT_TRUE(pfds[0].revents & POLLHUP); +} + +/* + * After initial opening/checks of hidraw, revoke the hidraw + * node and check that we can not read any more data. + */ +TEST_F(hidraw, write_event_revoked) +{ + struct timespec time_to_wait; + __u8 buf[10] = {0}; + int err; + + /* inject one event from hidraw */ + buf[0] = 1; /* report ID */ + buf[1] = 2; + buf[2] = 42; + + 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 = write(self->hidraw_fd, buf, 3); + ASSERT_EQ(err, 3) TH_LOG("unexpected error while writing to hidraw node: %d", err); + + err = pthread_cond_timedwait(&uhid_output_cond, &uhid_output_mtx, &time_to_wait); + ASSERT_OK(err) TH_LOG("error while calling waiting for the condition"); + + ASSERT_EQ(output_report[0], 1); + ASSERT_EQ(output_report[1], 2); + ASSERT_EQ(output_report[2], 42); + + /* call the revoke ioctl */ + err = ioctl(self->hidraw_fd, HIDIOCREVOKE, NULL); + ASSERT_OK(err) TH_LOG("couldn't revoke the hidraw fd"); + + /* inject one other event */ + buf[0] = 1; + buf[1] = 43; + err = write(self->hidraw_fd, buf, 3); + ASSERT_LT(err, 0) TH_LOG("unexpected success while writing to hidraw node: %d", err); + ASSERT_EQ(errno, ENODEV) TH_LOG("unexpected error code while writing to hidraw node: %d", + errno); + + pthread_mutex_unlock(&uhid_output_mtx); +} + +/* + * Test HIDIOCGRDESCSIZE ioctl to get report descriptor size + */ +TEST_F(hidraw, ioctl_rdescsize) +{ + int desc_size = 0; + int err; + + /* call HIDIOCGRDESCSIZE ioctl */ + err = ioctl(self->hidraw_fd, HIDIOCGRDESCSIZE, &desc_size); + ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRDESCSIZE ioctl failed"); + + /* verify the size matches our test report descriptor */ + ASSERT_EQ(desc_size, sizeof(rdesc)) + TH_LOG("expected size %zu, got %d", sizeof(rdesc), desc_size); +} + +/* + * Test HIDIOCGRDESC ioctl to get report descriptor data + */ +TEST_F(hidraw, ioctl_rdesc) +{ + struct hidraw_report_descriptor desc; + int err; + + /* get the full report descriptor */ + desc.size = sizeof(rdesc); + err = ioctl(self->hidraw_fd, HIDIOCGRDESC, &desc); + ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRDESC ioctl failed"); + + /* verify the descriptor data matches our test descriptor */ + ASSERT_EQ(memcmp(desc.value, rdesc, sizeof(rdesc)), 0) + TH_LOG("report descriptor data mismatch"); +} + +/* + * Test HIDIOCGRDESC ioctl with smaller buffer size + */ +TEST_F(hidraw, ioctl_rdesc_small_buffer) +{ + struct hidraw_report_descriptor desc; + int err; + size_t small_size = sizeof(rdesc) / 2; /* request half the descriptor size */ + + /* get partial report descriptor */ + desc.size = small_size; + err = ioctl(self->hidraw_fd, HIDIOCGRDESC, &desc); + ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRDESC ioctl failed with small buffer"); + + /* verify we got the first part of the descriptor */ + ASSERT_EQ(memcmp(desc.value, rdesc, small_size), 0) + TH_LOG("partial report descriptor data mismatch"); +} + +/* + * Test HIDIOCGRAWINFO ioctl to get device information + */ +TEST_F(hidraw, ioctl_rawinfo) +{ + struct hidraw_devinfo devinfo; + int err; + + /* get device info */ + err = ioctl(self->hidraw_fd, HIDIOCGRAWINFO, &devinfo); + ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRAWINFO ioctl failed"); + + /* verify device info matches our test setup */ + ASSERT_EQ(devinfo.bustype, BUS_USB) + TH_LOG("expected bustype 0x03, got 0x%x", devinfo.bustype); + ASSERT_EQ(devinfo.vendor, 0x0001) + TH_LOG("expected vendor 0x0001, got 0x%x", devinfo.vendor); + ASSERT_EQ(devinfo.product, 0x0a37) + TH_LOG("expected product 0x0a37, got 0x%x", devinfo.product); +} + +/* + * Test HIDIOCGFEATURE ioctl to get feature report + */ +TEST_F(hidraw, ioctl_gfeature) +{ + __u8 buf[10] = {0}; + int err; + + /* set report ID 1 in first byte */ + buf[0] = 1; + + /* get feature report */ + err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf); + ASSERT_EQ(err, sizeof(feature_data)) TH_LOG("HIDIOCGFEATURE ioctl failed, got %d", err); + + /* verify we got the expected feature data */ + ASSERT_EQ(buf[0], feature_data[0]) + TH_LOG("expected feature_data[0] = %d, got %d", feature_data[0], buf[0]); + ASSERT_EQ(buf[1], feature_data[1]) + TH_LOG("expected feature_data[1] = %d, got %d", feature_data[1], buf[1]); +} + +/* + * Test HIDIOCGFEATURE ioctl with invalid report ID + */ +TEST_F(hidraw, ioctl_gfeature_invalid) +{ + __u8 buf[10] = {0}; + int err; + + /* set invalid report ID (not 1) */ + buf[0] = 2; + + /* try to get feature report */ + err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf); + ASSERT_LT(err, 0) TH_LOG("HIDIOCGFEATURE should have failed with invalid report ID"); + ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno); +} + +/* + * Test ioctl with incorrect nr bits + */ +TEST_F(hidraw, ioctl_invalid_nr) +{ + char buf[256] = {0}; + int err; + unsigned int bad_cmd; + + /* + * craft an ioctl command with wrong _IOC_NR bits + */ + bad_cmd = _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x00, sizeof(buf)); /* 0 is not valid */ + + /* test the ioctl */ + err = ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("ioctl read-write with wrong _IOC_NR (0) should have failed"); + ASSERT_EQ(errno, ENOTTY) + TH_LOG("expected ENOTTY for wrong read-write _IOC_NR (0), got errno %d", errno); + + /* + * craft an ioctl command with wrong _IOC_NR bits + */ + bad_cmd = _IOC(_IOC_READ, 'H', 0x00, sizeof(buf)); /* 0 is not valid */ + + /* test the ioctl */ + err = ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("ioctl read-only with wrong _IOC_NR (0) should have failed"); + ASSERT_EQ(errno, ENOTTY) + TH_LOG("expected ENOTTY for wrong read-only _IOC_NR (0), got errno %d", errno); + + /* also test with bigger number */ + bad_cmd = _IOC(_IOC_READ, 'H', 0x42, sizeof(buf)); /* 0x42 is not valid as well */ + + err = ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("ioctl read-only with wrong _IOC_NR (0x42) should have failed"); + ASSERT_EQ(errno, ENOTTY) + TH_LOG("expected ENOTTY for wrong read-only _IOC_NR (0x42), got errno %d", errno); + + /* also test with bigger number: 0x42 is not valid as well */ + bad_cmd = _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x42, sizeof(buf)); + + err = ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("ioctl read-write with wrong _IOC_NR (0x42) should have failed"); + ASSERT_EQ(errno, ENOTTY) + TH_LOG("expected ENOTTY for wrong read-write _IOC_NR (0x42), got errno %d", errno); +} + +/* + * Test ioctl with incorrect type bits + */ +TEST_F(hidraw, ioctl_invalid_type) +{ + char buf[256] = {0}; + int err; + unsigned int bad_cmd; + + /* + * craft an ioctl command with wrong _IOC_TYPE bits + */ + bad_cmd = _IOC(_IOC_WRITE|_IOC_READ, 'I', 0x01, sizeof(buf)); /* 'I' should be 'H' */ + + /* test the ioctl */ + err = ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("ioctl with wrong _IOC_TYPE (I) should have failed"); + ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_NR, got errno %d", errno); +} + +/* + * Test HIDIOCGFEATURE ioctl with incorrect _IOC_DIR bits + */ +TEST_F(hidraw, ioctl_gfeature_invalid_dir) +{ + __u8 buf[10] = {0}; + int err; + unsigned int bad_cmd; + + /* set report ID 1 in first byte */ + buf[0] = 1; + + /* + * craft an ioctl command with wrong _IOC_DIR bits + * HIDIOCGFEATURE should have _IOC_WRITE|_IOC_READ, let's use only _IOC_WRITE + */ + bad_cmd = _IOC(_IOC_WRITE, 'H', 0x07, sizeof(buf)); /* should be _IOC_WRITE|_IOC_READ */ + + /* try to get feature report with wrong direction bits */ + err = ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("HIDIOCGFEATURE with wrong _IOC_DIR should have failed"); + ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got errno %d", errno); + + /* also test with only _IOC_READ */ + bad_cmd = _IOC(_IOC_READ, 'H', 0x07, sizeof(buf)); /* should be _IOC_WRITE|_IOC_READ */ + + err = ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("HIDIOCGFEATURE with wrong _IOC_DIR should have failed"); + ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got errno %d", errno); +} + +/* + * Test read-only ioctl with incorrect _IOC_DIR bits + */ +TEST_F(hidraw, ioctl_readonly_invalid_dir) +{ + char buf[256] = {0}; + int err; + unsigned int bad_cmd; + + /* + * craft an ioctl command with wrong _IOC_DIR bits + * HIDIOCGRAWNAME should have _IOC_READ, let's use _IOC_WRITE + */ + bad_cmd = _IOC(_IOC_WRITE, 'H', 0x04, sizeof(buf)); /* should be _IOC_READ */ + + /* try to get device name with wrong direction bits */ + err = ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("HIDIOCGRAWNAME with wrong _IOC_DIR should have failed"); + ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got errno %d", errno); + + /* also test with _IOC_WRITE|_IOC_READ */ + bad_cmd = _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x04, sizeof(buf)); /* should be only _IOC_READ */ + + err = ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("HIDIOCGRAWNAME with wrong _IOC_DIR should have failed"); + ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got errno %d", errno); +} + +/* + * Test HIDIOCSFEATURE ioctl to set feature report + */ +TEST_F(hidraw, ioctl_sfeature) +{ + __u8 buf[10] = {0}; + int err; + + /* prepare feature report data */ + buf[0] = 1; /* report ID */ + buf[1] = 0x42; + buf[2] = 0x24; + + /* set feature report */ + err = ioctl(self->hidraw_fd, HIDIOCSFEATURE(3), buf); + ASSERT_EQ(err, 3) TH_LOG("HIDIOCSFEATURE ioctl failed, got %d", err); + + /* + * Note: The uhid mock doesn't validate the set report data, + * so we just verify the ioctl succeeds + */ +} + +/* + * Test HIDIOCGINPUT ioctl to get input report + */ +TEST_F(hidraw, ioctl_ginput) +{ + __u8 buf[10] = {0}; + int err; + + /* set report ID 1 in first byte */ + buf[0] = 1; + + /* get input report */ + err = ioctl(self->hidraw_fd, HIDIOCGINPUT(sizeof(buf)), buf); + ASSERT_EQ(err, sizeof(feature_data)) TH_LOG("HIDIOCGINPUT ioctl failed, got %d", err); + + /* verify we got the expected input data */ + ASSERT_EQ(buf[0], feature_data[0]) + TH_LOG("expected feature_data[0] = %d, got %d", feature_data[0], buf[0]); + ASSERT_EQ(buf[1], feature_data[1]) + TH_LOG("expected feature_data[1] = %d, got %d", feature_data[1], buf[1]); +} + +/* + * Test HIDIOCGINPUT ioctl with invalid report ID + */ +TEST_F(hidraw, ioctl_ginput_invalid) +{ + __u8 buf[10] = {0}; + int err; + + /* set invalid report ID (not 1) */ + buf[0] = 2; + + /* try to get input report */ + err = ioctl(self->hidraw_fd, HIDIOCGINPUT(sizeof(buf)), buf); + ASSERT_LT(err, 0) TH_LOG("HIDIOCGINPUT should have failed with invalid report ID"); + ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno); +} + +/* + * Test HIDIOCSINPUT ioctl to set input report + */ +TEST_F(hidraw, ioctl_sinput) +{ + __u8 buf[10] = {0}; + int err; + + /* prepare input report data */ + buf[0] = 1; /* report ID */ + buf[1] = 0x55; + buf[2] = 0xAA; + + /* set input report */ + err = ioctl(self->hidraw_fd, HIDIOCSINPUT(3), buf); + ASSERT_EQ(err, 3) TH_LOG("HIDIOCSINPUT ioctl failed, got %d", err); + + /* + * Note: The uhid mock doesn't validate the set report data, + * so we just verify the ioctl succeeds + */ +} + +/* + * Test HIDIOCGOUTPUT ioctl to get output report + */ +TEST_F(hidraw, ioctl_goutput) +{ + __u8 buf[10] = {0}; + int err; + + /* set report ID 1 in first byte */ + buf[0] = 1; + + /* get output report */ + err = ioctl(self->hidraw_fd, HIDIOCGOUTPUT(sizeof(buf)), buf); + ASSERT_EQ(err, sizeof(feature_data)) TH_LOG("HIDIOCGOUTPUT ioctl failed, got %d", err); + + /* verify we got the expected output data */ + ASSERT_EQ(buf[0], feature_data[0]) + TH_LOG("expected feature_data[0] = %d, got %d", feature_data[0], buf[0]); + ASSERT_EQ(buf[1], feature_data[1]) + TH_LOG("expected feature_data[1] = %d, got %d", feature_data[1], buf[1]); +} + +/* + * Test HIDIOCGOUTPUT ioctl with invalid report ID + */ +TEST_F(hidraw, ioctl_goutput_invalid) +{ + __u8 buf[10] = {0}; + int err; + + /* set invalid report ID (not 1) */ + buf[0] = 2; + + /* try to get output report */ + err = ioctl(self->hidraw_fd, HIDIOCGOUTPUT(sizeof(buf)), buf); + ASSERT_LT(err, 0) TH_LOG("HIDIOCGOUTPUT should have failed with invalid report ID"); + ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno); +} + +/* + * Test HIDIOCSOUTPUT ioctl to set output report + */ +TEST_F(hidraw, ioctl_soutput) +{ + __u8 buf[10] = {0}; + int err; + + /* prepare output report data */ + buf[0] = 1; /* report ID */ + buf[1] = 0x33; + buf[2] = 0xCC; + + /* set output report */ + err = ioctl(self->hidraw_fd, HIDIOCSOUTPUT(3), buf); + ASSERT_EQ(err, 3) TH_LOG("HIDIOCSOUTPUT ioctl failed, got %d", err); + + /* + * Note: The uhid mock doesn't validate the set report data, + * so we just verify the ioctl succeeds + */ +} + +/* + * Test HIDIOCGRAWNAME ioctl to get device name string + */ +TEST_F(hidraw, ioctl_rawname) +{ + char name[256] = {0}; + char expected_name[64]; + int err; + + /* get device name */ + err = ioctl(self->hidraw_fd, HIDIOCGRAWNAME(sizeof(name)), name); + ASSERT_GT(err, 0) TH_LOG("HIDIOCGRAWNAME ioctl failed, got %d", err); + + /* construct expected name based on device id */ + snprintf(expected_name, sizeof(expected_name), "test-uhid-device-%d", self->hid.dev_id); + + /* verify the name matches expected pattern */ + ASSERT_EQ(strcmp(name, expected_name), 0) + TH_LOG("expected name '%s', got '%s'", expected_name, name); +} + +/* + * Test HIDIOCGRAWPHYS ioctl to get device physical address string + */ +TEST_F(hidraw, ioctl_rawphys) +{ + char phys[256] = {0}; + char expected_phys[64]; + int err; + + /* get device physical address */ + err = ioctl(self->hidraw_fd, HIDIOCGRAWPHYS(sizeof(phys)), phys); + ASSERT_GT(err, 0) TH_LOG("HIDIOCGRAWPHYS ioctl failed, got %d", err); + + /* construct expected phys based on device id */ + snprintf(expected_phys, sizeof(expected_phys), "%d", self->hid.dev_id); + + /* verify the phys matches expected value */ + ASSERT_EQ(strcmp(phys, expected_phys), 0) + TH_LOG("expected phys '%s', got '%s'", expected_phys, phys); +} + +/* + * Test HIDIOCGRAWUNIQ ioctl to get device unique identifier string + */ +TEST_F(hidraw, ioctl_rawuniq) +{ + char uniq[256] = {0}; + int err; + + /* get device unique identifier */ + err = ioctl(self->hidraw_fd, HIDIOCGRAWUNIQ(sizeof(uniq)), uniq); + ASSERT_GE(err, 0) TH_LOG("HIDIOCGRAWUNIQ ioctl failed, got %d", err); + + /* uniq is typically empty in our test setup */ + ASSERT_EQ(strlen(uniq), 0) TH_LOG("expected empty uniq, got '%s'", uniq); +} + +/* + * Test device string ioctls with small buffer sizes + */ +TEST_F(hidraw, ioctl_strings_small_buffer) +{ + char small_buf[8] = {0}; + char expected_name[64]; + int err; + + /* test HIDIOCGRAWNAME with small buffer */ + err = ioctl(self->hidraw_fd, HIDIOCGRAWNAME(sizeof(small_buf)), small_buf); + ASSERT_EQ(err, sizeof(small_buf)) + TH_LOG("HIDIOCGRAWNAME with small buffer failed, got %d", err); + + /* construct expected truncated name */ + snprintf(expected_name, sizeof(expected_name), "test-uhid-device-%d", self->hid.dev_id); + + /* verify we got truncated name (first 8 chars, no null terminator guaranteed) */ + ASSERT_EQ(strncmp(small_buf, expected_name, sizeof(small_buf)), 0) + TH_LOG("expected truncated name to match first %zu chars", sizeof(small_buf)); + + /* Note: hidraw driver doesn't guarantee null termination when buffer is too small */ +} + +int main(int argc, char **argv) +{ + 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 000000000..361dc7eaa --- /dev/null +++ b/tools/testing/selftests/hid/progs/hid.c @@ -0,0 +1,641 @@ +// 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; +__u64 get_data_overflow_check; + +SEC("?struct_ops/hid_device_event") +int BPF_PROG(hid_first_event, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type) +{ + __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(".struct_ops.link") +struct hid_bpf_ops first_event = { + .hid_device_event = (void *)hid_first_event, + .hid_id = 2, +}; + +int __hid_subprog_first_event(struct hid_bpf_ctx *hid_ctx, enum hid_report_type type) +{ + __u8 *rw_data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 3 /* size */); + + if (!rw_data) + return 0; /* EPERM check */ + + rw_data[2] = rw_data[1] + 5; + + return hid_ctx->size; +} + +SEC("?struct_ops/hid_device_event") +int BPF_PROG(hid_subprog_first_event, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type) +{ + return __hid_subprog_first_event(hid_ctx, type); +} + +SEC(".struct_ops.link") +struct hid_bpf_ops subprog_first_event = { + .hid_device_event = (void *)hid_subprog_first_event, + .hid_id = 2, +}; + +SEC("?struct_ops/hid_device_event") +int BPF_PROG(hid_second_event, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type) +{ + __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(".struct_ops.link") +struct hid_bpf_ops second_event = { + .hid_device_event = (void *)hid_second_event, +}; + +SEC("?struct_ops/hid_device_event") +int BPF_PROG(hid_change_report_id, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type) +{ + __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(".struct_ops.link") +struct hid_bpf_ops change_report_id = { + .hid_device_event = (void *)hid_change_report_id, +}; + +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 */ +}; + +/* + * the following program is marked as sleepable (struct_ops.s). + * This is not strictly mandatory but is a nice test for + * sleepable struct_ops + */ +SEC("?struct_ops.s/hid_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(".struct_ops.link") +struct hid_bpf_ops rdesc_fixup = { + .hid_rdesc_fixup = (void *)hid_rdesc_fixup, +}; + +SEC("?struct_ops.s/hid_rdesc_fixup") +int BPF_PROG(hid_rdesc_fixup_get_data_overflow, struct hid_bpf_ctx *hid_ctx) +{ + if (!hid_bpf_get_data(hid_ctx, 2 /* offset */, ~0ULL /* size */)) + get_data_overflow_check = 1; + + return 0; +} + +SEC(".struct_ops.link") +struct hid_bpf_ops rdesc_fixup_get_data_overflow = { + .hid_rdesc_fixup = (void *)hid_rdesc_fixup_get_data_overflow, +}; + +SEC("?struct_ops.s/hid_rdesc_fixup") +int BPF_PROG(hid_rdesc_fixup_change_uniq_name_phys, struct hid_bpf_ctx *hid_ctx) +{ +#define HID_BPF_MEMCPY(target, str) \ + __builtin_memcpy(target, str, sizeof(str)) + + HID_BPF_MEMCPY(hid_ctx->hid->name, "name coming from bpf"); + HID_BPF_MEMCPY(hid_ctx->hid->uniq, "uniq:coming:from:bpf"); + /* hid_bpf relies on a phys being a rand % 1024 */ + for (int i = 0; i < 5; i++) { + if (!hid_ctx->hid->phys[i]) { + HID_BPF_MEMCPY(hid_ctx->hid->phys + i, " phys:coming:from:bpf"); + break; + } + } + +#undef HID_BPF_MEMCPY + + return 0; +} + +SEC(".struct_ops.link") +struct hid_bpf_ops rdesc_fixup_change_uniq_name_phys = { + .hid_rdesc_fixup = (void *)hid_rdesc_fixup_change_uniq_name_phys, +}; + +SEC("?struct_ops/hid_device_event") +int BPF_PROG(hid_test_insert1, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type) +{ + __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(".struct_ops.link") +struct hid_bpf_ops test_insert1 = { + .hid_device_event = (void *)hid_test_insert1, + .flags = BPF_F_BEFORE, +}; + +SEC("?struct_ops/hid_device_event") +int BPF_PROG(hid_test_insert2, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type) +{ + __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(".struct_ops.link") +struct hid_bpf_ops test_insert2 = { + .hid_device_event = (void *)hid_test_insert2, +}; + +SEC("?struct_ops/hid_device_event") +int BPF_PROG(hid_test_insert3, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type) +{ + __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; +} + +SEC(".struct_ops.link") +struct hid_bpf_ops test_insert3 = { + .hid_device_event = (void *)hid_test_insert3, +}; + +SEC("?struct_ops/hid_hw_request") +int BPF_PROG(hid_test_filter_raw_request, struct hid_bpf_ctx *hctx, unsigned char reportnum, + enum hid_report_type rtype, enum hid_class_request reqtype, __u64 source) +{ + return -20; +} + +SEC(".struct_ops.link") +struct hid_bpf_ops test_filter_raw_request = { + .hid_hw_request = (void *)hid_test_filter_raw_request, +}; + +static struct file *current_file; + +SEC("fentry/hidraw_open") +int BPF_PROG(hidraw_open, struct inode *inode, struct file *file) +{ + current_file = file; + return 0; +} + +SEC("?struct_ops.s/hid_hw_request") +int BPF_PROG(hid_test_hidraw_raw_request, struct hid_bpf_ctx *hctx, unsigned char reportnum, + enum hid_report_type rtype, enum hid_class_request reqtype, __u64 source) +{ + __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 3 /* size */); + int ret; + + if (!data) + return 0; /* EPERM check */ + + /* check if the incoming request comes from our hidraw operation */ + if (source == (__u64)current_file) { + data[0] = reportnum; + + ret = hid_bpf_hw_request(hctx, data, 2, rtype, reqtype); + if (ret != 2) + return -1; + data[0] = reportnum + 1; + data[1] = reportnum + 2; + data[2] = reportnum + 3; + return 3; + } + + return 0; +} + +SEC(".struct_ops.link") +struct hid_bpf_ops test_hidraw_raw_request = { + .hid_hw_request = (void *)hid_test_hidraw_raw_request, +}; + +SEC("?struct_ops.s/hid_hw_request") +int BPF_PROG(hid_test_infinite_loop_raw_request, struct hid_bpf_ctx *hctx, unsigned char reportnum, + enum hid_report_type rtype, enum hid_class_request reqtype, __u64 source) +{ + __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 3 /* size */); + int ret; + + if (!data) + return 0; /* EPERM check */ + + /* always forward the request as-is to the device, hid-bpf should prevent + * infinite loops. + */ + data[0] = reportnum; + + ret = hid_bpf_hw_request(hctx, data, 2, rtype, reqtype); + if (ret == 2) + return 3; + + return 0; +} + +SEC(".struct_ops.link") +struct hid_bpf_ops test_infinite_loop_raw_request = { + .hid_hw_request = (void *)hid_test_infinite_loop_raw_request, +}; + +SEC("?struct_ops/hid_hw_output_report") +int BPF_PROG(hid_test_filter_output_report, struct hid_bpf_ctx *hctx, unsigned char reportnum, + enum hid_report_type rtype, enum hid_class_request reqtype, __u64 source) +{ + return -25; +} + +SEC(".struct_ops.link") +struct hid_bpf_ops test_filter_output_report = { + .hid_hw_output_report = (void *)hid_test_filter_output_report, +}; + +SEC("?struct_ops.s/hid_hw_output_report") +int BPF_PROG(hid_test_hidraw_output_report, struct hid_bpf_ctx *hctx, __u64 source) +{ + __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 3 /* size */); + int ret; + + if (!data) + return 0; /* EPERM check */ + + /* check if the incoming request comes from our hidraw operation */ + if (source == (__u64)current_file) + return hid_bpf_hw_output_report(hctx, data, 2); + + return 0; +} + +SEC(".struct_ops.link") +struct hid_bpf_ops test_hidraw_output_report = { + .hid_hw_output_report = (void *)hid_test_hidraw_output_report, +}; + +SEC("?struct_ops.s/hid_hw_output_report") +int BPF_PROG(hid_test_infinite_loop_output_report, struct hid_bpf_ctx *hctx, __u64 source) +{ + __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 3 /* size */); + int ret; + + if (!data) + return 0; /* EPERM check */ + + /* always forward the request as-is to the device, hid-bpf should prevent + * infinite loops. + */ + + ret = hid_bpf_hw_output_report(hctx, data, 2); + if (ret == 2) + return 2; + + return 0; +} + +SEC(".struct_ops.link") +struct hid_bpf_ops test_infinite_loop_output_report = { + .hid_hw_output_report = (void *)hid_test_infinite_loop_output_report, +}; + +struct elem { + struct bpf_wq work; +}; + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 1); + __type(key, int); + __type(value, struct elem); +} hmap SEC(".maps"); + +static int wq_cb_sleepable(void *map, int *key, void *work) +{ + __u8 buf[9] = {2, 3, 4, 5, 6, 7, 8, 9, 10}; + struct hid_bpf_ctx *hid_ctx; + + hid_ctx = hid_bpf_allocate_context(*key); + if (!hid_ctx) + return 0; /* EPERM check */ + + hid_bpf_input_report(hid_ctx, HID_INPUT_REPORT, buf, sizeof(buf)); + + hid_bpf_release_context(hid_ctx); + + return 0; +} + +static int test_inject_input_report_callback(int *key) +{ + struct elem init = {}, *val; + struct bpf_wq *wq; + + if (bpf_map_update_elem(&hmap, key, &init, 0)) + return -1; + + val = bpf_map_lookup_elem(&hmap, key); + if (!val) + return -2; + + wq = &val->work; + if (bpf_wq_init(wq, &hmap, 0) != 0) + return -3; + + if (bpf_wq_set_callback(wq, wq_cb_sleepable, 0)) + return -4; + + if (bpf_wq_start(wq, 0)) + return -5; + + return 0; +} + +SEC("?struct_ops/hid_device_event") +int BPF_PROG(hid_test_multiply_events_wq, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type) +{ + __u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 9 /* size */); + int hid = hid_ctx->hid->id; + int ret; + + if (!data) + return 0; /* EPERM check */ + + if (data[0] != 1) + return 0; + + ret = test_inject_input_report_callback(&hid); + if (ret) + return ret; + + data[1] += 5; + + return 0; +} + +SEC(".struct_ops.link") +struct hid_bpf_ops test_multiply_events_wq = { + .hid_device_event = (void *)hid_test_multiply_events_wq, +}; + +SEC("?struct_ops/hid_device_event") +int BPF_PROG(hid_test_multiply_events, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type) +{ + __u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 9 /* size */); + __u8 buf[9]; + int ret; + + if (!data) + return 0; /* EPERM check */ + + if (data[0] != 1) + return 0; + + /* + * we have to use an intermediate buffer as hid_bpf_input_report + * will memset data to \0 + */ + __builtin_memcpy(buf, data, sizeof(buf)); + + buf[0] = 2; + buf[1] += 5; + ret = hid_bpf_try_input_report(hid_ctx, HID_INPUT_REPORT, buf, sizeof(buf)); + if (ret < 0) + return ret; + + /* + * In real world we should reset the original buffer as data might be garbage now, + * but it actually now has the content of 'buf' + */ + data[1] += 5; + + return 9; +} + +SEC(".struct_ops.link") +struct hid_bpf_ops test_multiply_events = { + .hid_device_event = (void *)hid_test_multiply_events, +}; + +SEC("?struct_ops/hid_device_event") +int BPF_PROG(hid_test_infinite_loop_input_report, struct hid_bpf_ctx *hctx, + enum hid_report_type report_type, __u64 source) +{ + __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 6 /* size */); + __u8 buf[6]; + + if (!data) + return 0; /* EPERM check */ + + /* + * we have to use an intermediate buffer as hid_bpf_input_report + * will memset data to \0 + */ + __builtin_memcpy(buf, data, sizeof(buf)); + + /* always forward the request as-is to the device, hid-bpf should prevent + * infinite loops. + * the return value is ignored so the event is passing to userspace. + */ + + hid_bpf_try_input_report(hctx, report_type, buf, sizeof(buf)); + + /* each time we process the event, we increment by one data[1]: + * after each successful call to hid_bpf_try_input_report, buf + * has been memcopied into data by the kernel. + */ + data[1] += 1; + + return 0; +} + +SEC(".struct_ops.link") +struct hid_bpf_ops test_infinite_loop_input_report = { + .hid_device_event = (void *)hid_test_infinite_loop_input_report, +}; 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 000000000..056987937 --- /dev/null +++ b/tools/testing/selftests/hid/progs/hid_bpf_helpers.h @@ -0,0 +1,138 @@ +/* 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 bpf_wq bpf_wq___not_used +#define hid_bpf_ctx hid_bpf_ctx___not_used +#define hid_bpf_ops hid_bpf_ops___not_used +#define hid_device hid_device___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 + +/* do not define kfunc through vmlinux.h as this messes up our custom hack */ +#define BPF_NO_KFUNC_PROTOTYPES + +#include "vmlinux.h" + +#undef bpf_wq +#undef hid_bpf_ctx +#undef hid_bpf_ops +#undef hid_device +#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 + +#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_device { + unsigned int id; + char name[128]; + char phys[64]; + char uniq[64]; +} __attribute__((preserve_access_index)); + +struct bpf_wq { + __u64 __opaque[2]; +}; + +struct hid_bpf_ctx { + struct hid_device *hid; + __u32 allocated_size; + 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, +}; + +struct hid_bpf_ops { + int hid_id; + u32 flags; + struct list_head list; + int (*hid_device_event)(struct hid_bpf_ctx *ctx, enum hid_report_type report_type, + u64 source); + int (*hid_rdesc_fixup)(struct hid_bpf_ctx *ctx); + int (*hid_hw_request)(struct hid_bpf_ctx *ctx, unsigned char reportnum, + enum hid_report_type rtype, enum hid_class_request reqtype, + u64 source); + int (*hid_hw_output_report)(struct hid_bpf_ctx *ctx, u64 source); + struct hid_device *hdev; +}; + +#ifndef BPF_F_BEFORE +#define BPF_F_BEFORE (1U << 3) +#endif + +/* 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) __weak __ksym; +extern struct hid_bpf_ctx *hid_bpf_allocate_context(unsigned int hid_id) __weak __ksym; +extern void hid_bpf_release_context(struct hid_bpf_ctx *ctx) __weak __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) __weak __ksym; +extern int hid_bpf_hw_output_report(struct hid_bpf_ctx *ctx, + __u8 *buf, size_t buf__sz) __weak __ksym; +extern int hid_bpf_input_report(struct hid_bpf_ctx *ctx, + enum hid_report_type type, + __u8 *data, + size_t buf__sz) __weak __ksym; +extern int hid_bpf_try_input_report(struct hid_bpf_ctx *ctx, + enum hid_report_type type, + __u8 *data, + size_t buf__sz) __weak __ksym; + +/* bpf_wq implementation */ +extern int bpf_wq_init(struct bpf_wq *wq, void *p__map, unsigned int flags) __weak __ksym; +extern int bpf_wq_start(struct bpf_wq *wq, unsigned int flags) __weak __ksym; +extern int bpf_wq_set_callback(struct bpf_wq *wq, + int (*callback_fn)(void *, int *, void *), + unsigned int flags) __weak __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 000000000..af1682a53 --- /dev/null +++ b/tools/testing/selftests/hid/run-hid-tools-tests.sh @@ -0,0 +1,30 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# Runs tests for the HID subsystem + +KSELFTEST_SKIP_TEST=4 + +if ! command -v python3 > /dev/null 2>&1; then + echo "hid-tools: [SKIP] python3 not installed" + exit $KSELFTEST_SKIP_TEST +fi + +if ! python3 -c "import pytest" > /dev/null 2>&1; then + echo "hid: [SKIP] pytest module not installed" + exit $KSELFTEST_SKIP_TEST +fi + +if ! python3 -c "import pytest_tap" > /dev/null 2>&1; then + echo "hid: [SKIP] pytest_tap module not installed" + exit $KSELFTEST_SKIP_TEST +fi + +if ! python3 -c "import hidtools" > /dev/null 2>&1; then + echo "hid: [SKIP] hid-tools module not installed" + exit $KSELFTEST_SKIP_TEST +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 000000000..b3cbfc521 --- /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 000000000..c940e9275 --- /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 000000000..5175cf235 --- /dev/null +++ b/tools/testing/selftests/hid/tests/base.py @@ -0,0 +1,432 @@ +#!/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 dataclasses +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 + + +@dataclasses.dataclass +class HidBpf: + object_name: str + has_rdesc_fixup: bool + + +@dataclasses.dataclass +class KernelModule: + driver_name: str + module_name: str + + +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 KernelModule object, for example + # KernelModule("playstation", "hid-playstation") + kernel_modules: List[KernelModule] = [] + + # 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 HidBpf object, for example + # 'HidBpf("xppen-ArtistPro16Gen2.bpf.o", True)' + # If 'has_rdesc_fixup' is True, the test needs to wait + # for one unbind and rebind before it can be sure the kernel is + # ready + hid_bpfs: List[HidBpf] = [] + + 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 k in self.kernel_modules: + self._load_kernel_module(k.driver_name, k.module_name) + yield + + def load_hid_bpfs(self): + # this function will only work when run in the kernel tree + script_dir = Path(os.path.dirname(os.path.realpath(__file__))) + root_dir = (script_dir / "../../../../..").resolve() + bpf_dir = root_dir / "drivers/hid/bpf/progs" + + if not bpf_dir.exists(): + pytest.skip("looks like we are not in the kernel tree, skipping") + + udev_hid_bpf = shutil.which("udev-hid-bpf") + if not udev_hid_bpf: + pytest.skip("udev-hid-bpf not found in $PATH, skipping") + + wait = any(b.has_rdesc_fixup for b in self.hid_bpfs) + + 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.object_name), + ], + ) + while process.poll() is None: + self.uhdev.dispatch(1) + + if process.returncode != 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 000000000..59465c58d --- /dev/null +++ b/tools/testing/selftests/hid/tests/base_device.py @@ -0,0 +1,448 @@ +#!/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 dataclasses +import fcntl +import functools +import libevdev +import os +import threading + +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 + + +@dataclasses.dataclass +class HidReadiness: + is_ready: bool = False + count: int = 0 + + +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") -> HidReadiness: + """ + Overwrite in subclasses: should return True or False whether + the attached uhid device is ready or not. + """ + return HidReadiness() + + +class UdevHIDIsReady(HIDIsReady): + _pyudev_context: ClassVar[Optional[pyudev.Context]] = None + _pyudev_monitor: ClassVar[Optional[pyudev.Monitor]] = None + _uhid_devices: ClassVar[Dict[int, HidReadiness]] = {} + + 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) + + readiness = cls._uhid_devices.setdefault(id, HidReadiness()) + + ready = event.action == "bind" + if not readiness.is_ready and ready: + readiness.count += 1 + + readiness.is_ready = ready + + def is_ready(self: "UdevHIDIsReady") -> HidReadiness: + try: + return self._uhid_devices[self.uhid.hid_id] + except KeyError: + return HidReadiness() + + +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().is_ready and self.started + + @property + def kernel_ready_count(self: "BaseDevice") -> int: + return self._kernel_is_ready.is_ready().count + + @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 [] + + # Starting with kernel v6.16, an event is emitted when + # userspace opens a kernel device, and for some devices + # this translates into a SET_REPORT. + # Because EvdevDevice(path) opens every single evdev node + # we need to have a separate thread to process the incoming + # SET_REPORT or we end up having to wait for the kernel + # timeout of 5 seconds. + done = False + + def dispatch(): + while not done: + self.dispatch(1) + + t = threading.Thread(target=dispatch) + t.start() + + self._input_nodes = [ + EvdevDevice(path) + for path in self.walk_sysfs("input", "input/input*/event*") + ] + done = True + t.join() + 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 000000000..ec74d7576 --- /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 000000000..985a53532 --- /dev/null +++ b/tools/testing/selftests/hid/tests/conftest.py @@ -0,0 +1,95 @@ +#!/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 packaging.version import Version +import platform +import pytest +import re +import resource +import subprocess +from .base import HIDTestUdevRule +from pathlib import Path + + +@pytest.fixture(autouse=True) +def hidtools_version_check(): + HIDTOOLS_VERSION = "0.12" + try: + import hidtools + + version = hidtools.__version__ # type: ignore + if Version(version) < Version(HIDTOOLS_VERSION): + pytest.skip(reason=f"have hidtools {version}, require >={HIDTOOLS_VERSION}") + except Exception: + pytest.skip(reason=f"hidtools >={HIDTOOLS_VERSION} required") + + +# 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 000000000..91c16e005 --- /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 000000000..0e17588b9 --- /dev/null +++ b/tools/testing/selftests/hid/tests/test_apple_keyboard.py @@ -0,0 +1,441 @@ +#!/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 +from . import base + +import libevdev +import logging + +logger = logging.getLogger("hidtools.test.apple-keyboard") + +KERNEL_MODULE = base.KernelModule("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 000000000..612197805 --- /dev/null +++ b/tools/testing/selftests/hid/tests/test_gamepad.py @@ -0,0 +1,665 @@ +#!/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 +from .base import HidBpf + +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 = [HidBpf("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 000000000..9a7fe4002 --- /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 000000000..f695eaad1 --- /dev/null +++ b/tools/testing/selftests/hid/tests/test_ite_keyboard.py @@ -0,0 +1,167 @@ +#!/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 +from . import base + +logger = logging.getLogger("hidtools.test.ite-keyboard") + +KERNEL_MODULE = base.KernelModule("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 000000000..b3b2bdbf6 --- /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 000000000..eb4e15a0e --- /dev/null +++ b/tools/testing/selftests/hid/tests/test_mouse.py @@ -0,0 +1,1047 @@ +#!/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 BadReportDescriptorMouse(BaseMouse): + """ + This "device" was one autogenerated by syzbot. There are a lot of issues in + it, and the most problematic is that it declares features that have no + size. + + This leads to report->size being set to 0 and can mess up with usbhid + internals. Fortunately, uhid merely passes the incoming buffer, without + touching it so a buffer of size 0 will be translated to [] without + triggering a kernel oops. + + Because the report descriptor is wrong, no input are created, and we need + to tweak a little bit the parameters to make it look correct. + """ + + # fmt: off + report_descriptor = [ + 0x96, 0x01, 0x00, # Report Count (1) 0 + 0x06, 0x01, 0x00, # Usage Page (Generic Desktop) 3 + # 0x03, 0x00, 0x00, 0x00, 0x00, # Ignored by the kernel somehow + 0x2a, 0x90, 0xa0, # Usage Maximum (41104) 6 + 0x27, 0x00, 0x00, 0x00, 0x00, # Logical Maximum (0) 9 + 0xb3, 0x81, 0x3e, 0x25, 0x03, # Feature (Cnst,Arr,Abs,Vol) 14 + 0x1b, 0xdd, 0xe8, 0x40, 0x50, # Usage Minimum (1346431197) 19 + 0x3b, 0x5d, 0x8c, 0x3d, 0xda, # Designator Index 24 + ] + # fmt: on + + def __init__( + self, rdesc=report_descriptor, name=None, input_info=(3, 0x045E, 0x07DA) + ): + super().__init__(rdesc, name, input_info) + self.high_resolution_report_called = False + + def get_evdev(self, application=None): + assert self._input_nodes is None + return ( + "Ok" # should be a list or None, but both would fail, so abusing the system + ) + + def next_sync_events(self, application=None): + # there are no evdev nodes, so no events + return [] + + def is_ready(self): + # we wait for the SET_REPORT command to come + return self.high_resolution_report_called + + def set_report(self, req, rnum, rtype, data): + if rtype != self.UHID_FEATURE_REPORT: + raise InvalidHIDCommunication(f"Unexpected report type: {rtype}") + if rnum != 0x0: + raise InvalidHIDCommunication(f"Unexpected report number: {rnum}") + + if len(data) != 1: + raise InvalidHIDCommunication(f"Unexpected data: {data}, expected '[0]'") + + self.high_resolution_report_called = True + + return 0 + + +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 == [] + + +class TestBadReportDescriptorMouse(base.BaseTestCase.TestUhid): + def create_device(self): + return BadReportDescriptorMouse() + + def assertName(self, uhdev): + pass 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 000000000..789734011 --- /dev/null +++ b/tools/testing/selftests/hid/tests/test_multitouch.py @@ -0,0 +1,2292 @@ +#!/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 enum +import libevdev +import logging +import pytest +import sys +import time + +logger = logging.getLogger("hidtools.test.multitouch") + +KERNEL_MODULE = base.KernelModule("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 HIDButtonType(enum.IntEnum): + CLICKPAD = 0 + PRESSUREPAD = 1 + DISCRETE_BUTTONS = 2 + + +class PTP(Digitizer): + def __init__( + self, + name, + buttontype=HIDButtonType.CLICKPAD, + rdesc_str=None, + rdesc=None, + application="Touch Pad", + physical="Pointer", + max_contacts=None, + input_info=None, + ): + self.buttontype = buttontype + + 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 MinWin8TSParallelBigContactMax(Digitizer): + """A parallel Win8 touchscreen that advertises a ContactCountMaximum much + larger than the number of contacts it actually reports. + + Such firmware makes the driver allocate that many input slots (up to 255) + while the input report only carries a few contacts. This is what used to + drive the per-slot bit operations on mt_io_flags out of bounds. The number + of contacts a HID report can describe is limited by the descriptor size, + so a large ContactCountMaximum can only be expressed this way, decoupled + from the number of finger collections.""" + + def __init__(self, n_fingers=5, contact_max=250): + 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 * n_fingers} + 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 ({contact_max}) + Usage (Contact Max) + Feature (Data,Var,Abs) + End Collection + {Digitizer.msCertificationBlob(68)} +""" + super().__init__( + f"uhid test parallel big contact max {contact_max}", rdesc_str + ) + + +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_buttontype(self): + """Check for the right ButtonType.""" + uhdev = self.uhdev + assert uhdev is not None + evdev = uhdev.get_evdev() + + # If libevdev.so is not yet compiled with INPUT_PROP_PRESSUREPAD + # python-libevdev won't have it either, let's fake it + if not getattr(libevdev, "INPUT_PROP_PRESSUREPAD", None): + prop = libevdev.InputProperty(name="INPUT_PROP_PRESSUREPAD", value=0x7) + libevdev.INPUT_PROP_PRESSUREPAD = prop + libevdev.props.append(prop) + + if uhdev.buttontype == HIDButtonType.CLICKPAD: + assert libevdev.INPUT_PROP_BUTTONPAD in evdev.properties + elif uhdev.buttontype == HIDButtonType.PRESSUREPAD: + assert libevdev.INPUT_PROP_PRESSUREPAD in evdev.properties + else: + assert libevdev.INPUT_PROP_PRESSUREPAD not in evdev.properties + assert libevdev.INPUT_PROP_BUTTONPAD not in evdev.properties + + def test_ptp_buttons(self): + """check for button reliability. + There are 3 types of touchpads: click pads + pressure pads and + those with discrete buttons. Each should reliably report the BTN_LEFT events. + """ + uhdev = self.uhdev + evdev = uhdev.get_evdev() + + if uhdev.buttontype in [HIDButtonType.CLICKPAD, HIDButtonType.PRESSUREPAD]: + 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 + elif uhdev.buttontype == HIDButtonType.DISCRETE_BUTTONS: + 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 TestMinWin8TSParallelBigContactMax(base.BaseTestCase.TestUhid): + """Regression test for the out-of-bounds bit operations on + struct mt_device.mt_io_flags. + + A Win8 touchscreen may advertise a ContactCountMaximum much larger than + the number of contacts it reports. The driver used to keep the per-slot + active state in the bits of a single unsigned long while indexing + set_bit()/clear_bit() by the slot number, so such a device drove those bit + operations out of bounds. The sticky-fingers release timer made it fatal: + mt_release_contacts() cleared one bit per slot, overwrote the adjacent + struct mt_device members and panicked the kernel. + + Send a single contact, let the 100ms sticky-fingers timer release it, and + check that the kernel reports the release cleanly instead of crashing.""" + + kernel_modules = [KERNEL_MODULE] + + def create_device(self): + return MinWin8TSParallelBigContactMax() + + def test_sticky_fingers_release_big_contact_max(self): + uhdev = self.uhdev + evdev = uhdev.get_evdev() + + assert evdev.num_slots == uhdev.max_contacts + + 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 + + # do not release the contact; the sticky-fingers timer must do it + # after 100ms, which is where the out-of-bounds release used to hit + 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 + + +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 + + + @pytest.mark.skip_if_uhdev( + lambda uhdev: "Confidence" not in uhdev.fields, + "Device not compatible, missing Confidence usage", + ) + def test_mt_confidence_bad_multi_release(self): + """Check for the sticky finger being properly detected. + + We first inject 3 fingers, then release only the second. + After 100 ms, we should receive a generated event about the + 2 missing fingers being released. + """ + uhdev = self.uhdev + evdev = uhdev.get_evdev() + + # send 3 touches + t0 = Touch(1, 50, 10) + t1 = Touch(2, 150, 100) + t2 = Touch(3, 250, 200) + r = uhdev.event([t0, t1, t2]) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + + # release the second + t1.tipswitch = False + r = uhdev.event([t1]) + events = uhdev.next_sync_events() + self.debug_reports(r, uhdev, events) + + # only the second is released + 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 evdev.slots[2][libevdev.EV_ABS.ABS_MT_TRACKING_ID] != -1 + + # wait for the timer to kick in + time.sleep(0.2) + + events = uhdev.next_sync_events() + self.debug_reports([], uhdev, events) + + # now all 3 fingers are released + assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in 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 evdev.slots[2][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", + buttontype=HIDButtonType.DISCRETE_BUTTONS, + 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", + buttontype=HIDButtonType.DISCRETE_BUTTONS, + 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 Testven_0488_108c(BaseTest.TestPTP): + def create_device(self): + return PTP( + "uhid test ven_0488_108c", + rdesc="05 01 09 02 a1 01 85 06 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 03 05 01 09 30 09 31 09 38 15 81 25 7f 75 08 95 03 81 06 c0 c0 05 0d 09 05 a1 01 85 01 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 81 03 05 01 15 00 26 ba 0d 75 10 55 0e 65 11 09 30 35 00 46 d0 05 95 01 81 02 26 d0 06 46 bb 02 09 31 81 02 05 0d 95 01 75 10 26 ff 7f 46 ff 7f 09 30 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 81 03 05 01 15 00 26 ba 0d 75 10 55 0e 65 11 09 30 35 00 46 d0 05 95 01 81 02 26 d0 06 46 bb 02 09 31 81 02 05 0d 95 01 75 10 26 ff 7f 46 ff 7f 09 30 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 81 03 05 01 15 00 26 ba 0d 75 10 55 0e 65 11 09 30 35 00 46 d0 05 95 01 81 02 26 d0 06 46 bb 02 09 31 81 02 05 0d 95 01 75 10 26 ff 7f 46 ff 7f 09 30 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 09 01 25 01 75 01 95 01 81 02 95 07 81 03 05 0d 85 02 09 55 75 08 95 01 25 05 b1 02 09 59 b1 02 06 00 ff 85 03 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 05 0e 09 01 a1 02 85 13 09 23 15 00 25 64 75 08 95 01 b1 02 c0 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 05 09 57 09 58 75 01 95 02 25 01 b1 02 95 06 b1 03 c0 c0 06 01 ff 09 02 a1 01 09 00 85 07 15 00 26 ff 00 75 08 96 12 02 b1 02 c0 06 00 ff 09 01 a1 01 85 0d 15 00 26 ff 00 75 08 95 11 09 01 81 02 09 01 91 02 c0 05 0e 09 01 a1 01 85 11 09 35 15 00 26 ff 00 75 08 95 17 b1 02 c0 06 81 ff 09 01 a1 01 09 20 85 17 15 00 26 ff 00 75 08 95 3f 09 01 81 02 09 01 91 02 c0", + input_info=(0x18, 0x0488, 0x108C), + buttontype=HIDButtonType.PRESSUREPAD, + ) + + +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", + buttontype=HIDButtonType.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", + ) + +class Testsynaptics_06cb_ce26(TestWin8TSConfidence): + def create_device(self): + return PTP( + "uhid test synaptics_06cb_ce26", + max_contacts=5, + input_info=(BusType.I2C, 0x06CB, 0xCE26), + 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 45 05 75 10 55 0e 65 11 09 30 35 00 46 64 04 95 01 81 02 46 a2 02 26 29 03 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 45 05 75 10 55 0e 65 11 09 30 35 00 46 64 04 95 01 81 02 46 a2 02 26 29 03 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 45 05 75 10 55 0e 65 11 09 30 35 00 46 64 04 95 01 81 02 46 a2 02 26 29 03 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 45 05 75 10 55 0e 65 11 09 30 35 00 46 64 04 95 01 81 02 46 a2 02 26 29 03 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 45 05 75 10 55 0e 65 11 09 30 35 00 46 64 04 95 01 81 02 46 a2 02 26 29 03 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", + ) 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 000000000..7fd3a8e61 --- /dev/null +++ b/tools/testing/selftests/hid/tests/test_sony.py @@ -0,0 +1,343 @@ +#!/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 .base import KernelModule +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 = KernelModule("sony", "hid_sony") +PS4_MODULE = KernelModule("playstation", "hid_playstation") +PS5_MODULE = KernelModule("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 000000000..5b9abb616 --- /dev/null +++ b/tools/testing/selftests/hid/tests/test_tablet.py @@ -0,0 +1,1577 @@ +#!/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 +from .base import HidBpf +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.distance = -10 + 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", + "distance", + "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.distance = 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) + + @pytest.mark.skip_if_uhdev( + lambda uhdev: "Z" not in uhdev.fields, + "Device not compatible, missing Z 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().items()], + ) + def test_z_reported_as_distance(self, state_list, scribble): + """Verify stylus Z values are reported as ABS_DISTANCE.""" + self._test_states(state_list, scribble, allow_intermediate_states=False) + + uhdev = self.uhdev + evdev = uhdev.get_evdev() + p = Pen(0, 0) + uhdev.move_to(p, PenState.PEN_IS_OUT_OF_RANGE, None) + p = Pen(100, 200) + uhdev.move_to(p, PenState.PEN_IS_IN_RANGE, None) + p.distance = -1 + events = self.post(uhdev, p, None) + assert evdev.value[libevdev.EV_ABS.ABS_DISTANCE] == -1 + + +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 + + +class Wacom_2d1f_014b(PenDigitizer): + """ + Pen that reports distance values with HID_GD_Z usage. + """ + def __init__( + self, + name, + rdesc_str=None, + rdesc=None, + application="Pen", + physical="Stylus", + input_info=(BusType.USB, 0x2D1F, 0x014B), + evdev_name_suffix=None, + ): + super().__init__( + name, rdesc_str, rdesc, application, physical, input_info, evdev_name_suffix + ) + + def match_evdev_rule(self, application, evdev): + # there are 2 nodes created by the device, only one matters + return evdev.name.endswith("Stylus") + + def event(self, pen, test_button): + # this device reports the distance through Z + pen.z = pen.distance + + return super().event(pen, test_button) + + +################################################################################ +# +# 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 = [HidBpf("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 = [HidBpf("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 = [HidBpf("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), + ) + + +################################################################################ +# +# Devices Reporting Distance +# +################################################################################ + + +class TestWacom_2d1f_014b(BaseTest.TestTablet): + def create_device(self): + return Wacom_2d1f_014b( + "uhid test Wacom 2d1f_014b", + rdesc="05 0d 09 02 a1 01 85 02 09 20 a1 00 09 42 09 44 09 45 09 3c 09 5a 09 32 15 00 25 01 75 01 95 06 81 02 95 02 81 03 05 01 09 30 26 88 3e 46 88 3e 65 11 55 0d 75 10 95 01 81 02 09 31 26 60 53 46 60 53 81 02 05 0d 09 30 26 ff 0f 45 00 65 00 55 00 81 02 06 00 ff 09 04 75 08 26 ff 00 46 ff 00 65 11 55 0e 81 02 05 0d 09 3d 75 10 16 d8 dc 26 28 23 36 d8 dc 46 28 23 65 14 81 02 09 3e 81 02 05 01 09 32 16 01 ff 25 00 36 01 ff 45 00 65 11 81 02 05 0d 09 56 15 00 27 ff ff 00 00 35 00 47 ff ff 00 00 66 01 10 55 0c 81 02 45 00 65 00 55 00 c0 09 00 75 08 26 ff 00 b1 12 85 03 09 00 95 12 b1 12 85 05 09 00 95 04 b1 02 85 06 09 00 95 24 b1 02 85 16 09 00 15 00 26 ff 00 95 06 b1 02 85 17 09 00 95 0c b1 02 85 19 09 00 95 01 b1 02 85 0a 09 00 75 08 95 01 15 10 26 ff 00 b1 02 85 1e 09 00 95 10 b1 02 c0 06 00 ff 09 00 a1 01 85 09 05 0d 09 20 a1 00 09 00 15 00 26 ff 00 75 08 95 10 81 02 c0 09 00 95 03 b1 12 c0 06 00 ff 09 02 a1 01 85 07 09 00 96 09 01 b1 02 85 08 09 00 95 03 81 02 09 00 b1 02 85 0e 09 00 96 0a 01 b1 02 c0 05 0d 09 02 a1 01 85 1a 09 20 a1 00 09 42 09 44 09 45 09 3c 09 5a 09 32 15 00 25 01 75 01 95 06 81 02 09 38 25 03 75 02 95 01 81 02 05 01 09 30 26 88 3e 46 88 3e 65 11 55 0d 75 10 95 01 81 02 09 31 26 60 53 46 60 53 81 02 05 0d 09 30 26 ff 0f 46 b0 0f 66 11 e1 55 02 81 02 06 00 ff 09 04 75 08 26 ff 00 46 ff 00 65 11 55 0e 81 02 05 0d 09 3d 75 10 16 d8 dc 26 28 23 36 d8 dc 46 28 23 65 14 81 02 09 3e 81 02 05 01 09 32 16 01 ff 25 00 36 01 ff 45 00 65 11 81 02 05 0d 09 56 15 00 27 ff ff 00 00 35 00 47 ff ff 00 00 66 01 10 55 0c 81 02 45 00 65 00 55 00 c0 c0 06 00 ff 09 00 a1 01 85 1b 05 0d 09 20 a1 00 09 00 26 ff 00 75 08 95 10 81 02 c0 c0", + input_info=(BusType.USB, 0x2D1F, 0x014B), + ) 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 000000000..e98bff919 --- /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 000000000..3903f479b --- /dev/null +++ b/tools/testing/selftests/hid/tests/test_wacom_generic.py @@ -0,0 +1,1415 @@ +#!/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 = base.KernelModule("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) + + +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 + ) + + def test_prop_pointer(self): + """ + Verify that INPUT_PROP_POINTER is set and INPUT_PROP_DIRECT + is not set on opaque devices. + """ + evdev = self.uhdev.get_evdev() + assert libevdev.INPUT_PROP_POINTER in evdev.properties + assert libevdev.INPUT_PROP_DIRECT not in evdev.properties + + +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 DirectTabletTest(): + def test_prop_direct(self): + """ + Verify that INPUT_PROP_DIRECT is set and INPUT_PROP_POINTER + is not set on display devices. + """ + evdev = self.uhdev.get_evdev() + assert libevdev.INPUT_PROP_DIRECT in evdev.properties + assert libevdev.INPUT_PROP_POINTER not in evdev.properties + + +class TestDTH2452Tablet(test_multitouch.BaseTest.TestMultitouch, TouchTabletTest, DirectTabletTest): + 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, 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, evdev, data, t=0): + """ + Assert properties of a list of contacts generated by make_contacts. + """ + for contact_ids in data: + self.assert_contact(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( + 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(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 000000000..ecbd57f77 --- /dev/null +++ b/tools/testing/selftests/hid/vmtest.sh @@ -0,0 +1,474 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) 2025 Red Hat +# Copyright (c) 2025 Meta Platforms, Inc. and affiliates +# +# Dependencies: +# * virtme-ng +# * busybox-static (used by virtme-ng) +# * qemu (used by virtme-ng) + +readonly SCRIPT_DIR="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" +readonly KERNEL_CHECKOUT=$(realpath "${SCRIPT_DIR}"/../../../../) + +source "${SCRIPT_DIR}"/../kselftest/ktap_helpers.sh + +readonly HID_BPF_TEST="${SCRIPT_DIR}"/hid_bpf +readonly HIDRAW_TEST="${SCRIPT_DIR}"/hidraw +readonly HID_BPF_PROGS="${KERNEL_CHECKOUT}/drivers/hid/bpf/progs" +readonly SSH_GUEST_PORT=22 +readonly WAIT_PERIOD=3 +readonly WAIT_PERIOD_MAX=60 +readonly WAIT_TOTAL=$(( WAIT_PERIOD * WAIT_PERIOD_MAX )) +readonly QEMU_PIDFILE=$(mktemp /tmp/qemu_hid_vmtest_XXXX.pid) + +readonly QEMU_OPTS="\ + --pidfile ${QEMU_PIDFILE} \ +" +readonly KERNEL_CMDLINE="" +readonly LOG=$(mktemp /tmp/hid_vmtest_XXXX.log) +readonly TEST_NAMES=(vm_hid_bpf vm_hidraw vm_pytest) +readonly TEST_DESCS=( + "Run hid_bpf tests in the VM." + "Run hidraw tests in the VM." + "Run the hid-tools test-suite in the VM." +) + +VERBOSE=0 +SHELL_MODE=0 +BUILD_HOST="" +BUILD_HOST_PODMAN_CONTAINER_NAME="" + +usage() { + local name + local desc + local i + + echo + echo "$0 [OPTIONS] [TEST]... [-- tests-args]" + echo "If no TEST argument is given, all tests will be run." + echo + echo "Options" + echo " -b: build the kernel from the current source tree and use it for guest VMs" + echo " -H: hostname for remote build host (used with -b)" + echo " -p: podman container name for remote build host (used with -b)" + echo " Example: -H beefyserver -p vng" + echo " -q: set the path to or name of qemu binary" + echo " -s: start a shell in the VM instead of running tests" + echo " -v: more verbose output (can be repeated multiple times)" + echo + echo "Available tests" + + for ((i = 0; i < ${#TEST_NAMES[@]}; i++)); do + name=${TEST_NAMES[${i}]} + desc=${TEST_DESCS[${i}]} + printf "\t%-35s%-35s\n" "${name}" "${desc}" + done + echo + + exit 1 +} + +die() { + echo "$*" >&2 + exit "${KSFT_FAIL}" +} + +vm_ssh() { + # vng --ssh-client keeps shouting "Warning: Permanently added 'virtme-ng%22' + # (ED25519) to the list of known hosts.", + # So replace the command with what's actually called and add the "-q" option + stdbuf -oL ssh -q \ + -F ${HOME}/.cache/virtme-ng/.ssh/virtme-ng-ssh.conf \ + -l root virtme-ng%${SSH_GUEST_PORT} \ + "$@" + return $? +} + +cleanup() { + if [[ -s "${QEMU_PIDFILE}" ]]; then + pkill -SIGTERM -F "${QEMU_PIDFILE}" > /dev/null 2>&1 + fi + + # If failure occurred during or before qemu start up, then we need + # to clean this up ourselves. + if [[ -e "${QEMU_PIDFILE}" ]]; then + rm "${QEMU_PIDFILE}" + fi +} + +check_args() { + local found + + for arg in "$@"; do + found=0 + for name in "${TEST_NAMES[@]}"; do + if [[ "${name}" = "${arg}" ]]; then + found=1 + break + fi + done + + if [[ "${found}" -eq 0 ]]; then + echo "${arg} is not an available test" >&2 + usage + fi + done + + for arg in "$@"; do + if ! command -v > /dev/null "test_${arg}"; then + echo "Test ${arg} not found" >&2 + usage + fi + done +} + +check_deps() { + for dep in vng ${QEMU} busybox pkill ssh pytest; do + if [[ ! -x $(command -v "${dep}") ]]; then + echo -e "skip: dependency ${dep} not found!\n" + exit "${KSFT_SKIP}" + fi + done + + if [[ ! -x $(command -v "${HID_BPF_TEST}") ]]; then + printf "skip: %s not found!" "${HID_BPF_TEST}" + printf " Please build the kselftest hid_bpf target.\n" + exit "${KSFT_SKIP}" + fi + + if [[ ! -x $(command -v "${HIDRAW_TEST}") ]]; then + printf "skip: %s not found!" "${HIDRAW_TEST}" + printf " Please build the kselftest hidraw target.\n" + exit "${KSFT_SKIP}" + fi +} + +check_vng() { + local tested_versions + local version + local ok + + tested_versions=("1.36" "1.37") + version="$(vng --version)" + + ok=0 + for tv in "${tested_versions[@]}"; do + if [[ "${version}" == *"${tv}"* ]]; then + ok=1 + break + fi + done + + if [[ ! "${ok}" -eq 1 ]]; then + printf "warning: vng version '%s' has not been tested and may " "${version}" >&2 + printf "not function properly.\n\tThe following versions have been tested: " >&2 + echo "${tested_versions[@]}" >&2 + fi +} + +handle_build() { + if [[ ! "${BUILD}" -eq 1 ]]; then + return + fi + + if [[ ! -d "${KERNEL_CHECKOUT}" ]]; then + echo "-b requires vmtest.sh called from the kernel source tree" >&2 + exit 1 + fi + + pushd "${KERNEL_CHECKOUT}" &>/dev/null + + if ! vng --kconfig --config "${SCRIPT_DIR}"/config; then + die "failed to generate .config for kernel source tree (${KERNEL_CHECKOUT})" + fi + + local vng_args=("-v" "--config" "${SCRIPT_DIR}/config" "--build") + + if [[ -n "${BUILD_HOST}" ]]; then + vng_args+=("--build-host" "${BUILD_HOST}") + fi + + if [[ -n "${BUILD_HOST_PODMAN_CONTAINER_NAME}" ]]; then + vng_args+=("--build-host-exec-prefix" \ + "podman exec -ti ${BUILD_HOST_PODMAN_CONTAINER_NAME}") + fi + + if ! vng "${vng_args[@]}"; then + die "failed to build kernel from source tree (${KERNEL_CHECKOUT})" + fi + + if ! make -j$(nproc) -C "${HID_BPF_PROGS}"; then + die "failed to build HID bpf objects from source tree (${HID_BPF_PROGS})" + fi + + if ! make -j$(nproc) -C "${SCRIPT_DIR}"; then + die "failed to build HID selftests from source tree (${SCRIPT_DIR})" + fi + + popd &>/dev/null +} + +vm_start() { + local logfile=/dev/null + local verbose_opt="" + local kernel_opt="" + local qemu + + qemu=$(command -v "${QEMU}") + + if [[ "${VERBOSE}" -eq 2 ]]; then + verbose_opt="--verbose" + logfile=/dev/stdout + fi + + # If we are running from within the kernel source tree, use the kernel source tree + # as the kernel to boot, otherwise use the currently running kernel. + if [[ "$(realpath "$(pwd)")" == "${KERNEL_CHECKOUT}"* ]]; then + kernel_opt="${KERNEL_CHECKOUT}" + fi + + vng \ + --run \ + ${kernel_opt} \ + ${verbose_opt} \ + --qemu-opts="${QEMU_OPTS}" \ + --qemu="${qemu}" \ + --user root \ + --append "${KERNEL_CMDLINE}" \ + --ssh "${SSH_GUEST_PORT}" \ + --rw &> ${logfile} & + + local vng_pid=$! + local elapsed=0 + + while [[ ! -s "${QEMU_PIDFILE}" ]]; do + if ! kill -0 "${vng_pid}" 2>/dev/null; then + echo "vng process (PID ${vng_pid}) exited early, check logs for details" >&2 + die "failed to boot VM" + fi + + if [[ ${elapsed} -ge ${WAIT_TOTAL} ]]; then + echo "Timed out after ${WAIT_TOTAL} seconds waiting for VM to boot" >&2 + die "failed to boot VM" + fi + + sleep 1 + elapsed=$((elapsed + 1)) + done +} + +vm_wait_for_ssh() { + local i + + i=0 + while true; do + if [[ ${i} -gt ${WAIT_PERIOD_MAX} ]]; then + die "Timed out waiting for guest ssh" + fi + if vm_ssh -- true; then + break + fi + i=$(( i + 1 )) + sleep ${WAIT_PERIOD} + done +} + +vm_mount_bpffs() { + vm_ssh -- mount bpffs -t bpf /sys/fs/bpf +} + +__log_stdin() { + stdbuf -oL awk '{ printf "%s:\t%s\n","'"${prefix}"'", $0; fflush() }' +} + +__log_args() { + echo "$*" | awk '{ printf "%s:\t%s\n","'"${prefix}"'", $0 }' +} + +log() { + local verbose="$1" + shift + + local prefix="$1" + + shift + local redirect= + if [[ ${verbose} -le 0 ]]; then + redirect=/dev/null + else + redirect=/dev/stdout + fi + + if [[ "$#" -eq 0 ]]; then + __log_stdin | tee -a "${LOG}" > ${redirect} + else + __log_args "$@" | tee -a "${LOG}" > ${redirect} + fi +} + +log_setup() { + log $((VERBOSE-1)) "setup" "$@" +} + +log_host() { + local testname=$1 + + shift + log $((VERBOSE-1)) "test:${testname}:host" "$@" +} + +log_guest() { + local testname=$1 + + shift + log ${VERBOSE} "# test:${testname}" "$@" +} + +test_vm_hid_bpf() { + local testname="${FUNCNAME[0]#test_}" + + vm_ssh -- "${HID_BPF_TEST}" \ + 2>&1 | log_guest "${testname}" + + return ${PIPESTATUS[0]} +} + +test_vm_hidraw() { + local testname="${FUNCNAME[0]#test_}" + + vm_ssh -- "${HIDRAW_TEST}" \ + 2>&1 | log_guest "${testname}" + + return ${PIPESTATUS[0]} +} + +test_vm_pytest() { + local testname="${FUNCNAME[0]#test_}" + + shift + + vm_ssh -- pytest ${SCRIPT_DIR}/tests --color=yes "$@" \ + 2>&1 | log_guest "${testname}" + + return ${PIPESTATUS[0]} +} + +run_test() { + local vm_oops_cnt_before + local vm_warn_cnt_before + local vm_oops_cnt_after + local vm_warn_cnt_after + local name + local rc + + vm_oops_cnt_before=$(vm_ssh -- dmesg | grep -c -i 'Oops') + vm_error_cnt_before=$(vm_ssh -- dmesg --level=err | wc -l) + + name=$(echo "${1}" | awk '{ print $1 }') + eval test_"${name}" "$@" + rc=$? + + vm_oops_cnt_after=$(vm_ssh -- dmesg | grep -i 'Oops' | wc -l) + if [[ ${vm_oops_cnt_after} -gt ${vm_oops_cnt_before} ]]; then + echo "FAIL: kernel oops detected on vm" | log_host "${name}" + rc=$KSFT_FAIL + fi + + vm_error_cnt_after=$(vm_ssh -- dmesg --level=err | wc -l) + if [[ ${vm_error_cnt_after} -gt ${vm_error_cnt_before} ]]; then + echo "FAIL: kernel error detected on vm" | log_host "${name}" + vm_ssh -- dmesg --level=err | log_host "${name}" + rc=$KSFT_FAIL + fi + + return "${rc}" +} + +QEMU="qemu-system-$(uname -m)" + +while getopts :hvsbq:H:p: o +do + case $o in + v) VERBOSE=$((VERBOSE+1));; + s) SHELL_MODE=1;; + b) BUILD=1;; + q) QEMU=$OPTARG;; + H) BUILD_HOST=$OPTARG;; + p) BUILD_HOST_PODMAN_CONTAINER_NAME=$OPTARG;; + h|*) usage;; + esac +done +shift $((OPTIND-1)) + +trap cleanup EXIT + +PARAMS="" + +if [[ ${#} -eq 0 ]]; then + ARGS=("${TEST_NAMES[@]}") +else + ARGS=() + COUNT=0 + for arg in $@; do + COUNT=$((COUNT+1)) + if [[ x"$arg" == x"--" ]]; then + break + fi + ARGS+=($arg) + done + shift $COUNT + PARAMS="$@" +fi + +if [[ "${SHELL_MODE}" -eq 0 ]]; then + check_args "${ARGS[@]}" + echo "1..${#ARGS[@]}" +fi +check_deps +check_vng +handle_build + +log_setup "Booting up VM" +vm_start +vm_wait_for_ssh +vm_mount_bpffs +log_setup "VM booted up" + +if [[ "${SHELL_MODE}" -eq 1 ]]; then + log_setup "Starting interactive shell in VM" + echo "Starting shell in VM. Use 'exit' to quit and shutdown the VM." + CURRENT_DIR="$(pwd)" + vm_ssh -t -- "cd '${CURRENT_DIR}' && exec bash -l" + exit "$KSFT_PASS" +fi + +cnt_pass=0 +cnt_fail=0 +cnt_skip=0 +cnt_total=0 +for arg in "${ARGS[@]}"; do + run_test "${arg}" "${PARAMS}" + rc=$? + if [[ ${rc} -eq $KSFT_PASS ]]; then + cnt_pass=$(( cnt_pass + 1 )) + echo "ok ${cnt_total} ${arg}" + elif [[ ${rc} -eq $KSFT_SKIP ]]; then + cnt_skip=$(( cnt_skip + 1 )) + echo "ok ${cnt_total} ${arg} # SKIP" + elif [[ ${rc} -eq $KSFT_FAIL ]]; then + cnt_fail=$(( cnt_fail + 1 )) + echo "not ok ${cnt_total} ${arg} # exit=$rc" + fi + cnt_total=$(( cnt_total + 1 )) +done + +echo "SUMMARY: PASS=${cnt_pass} SKIP=${cnt_skip} FAIL=${cnt_fail}" +echo "Log: ${LOG}" + +if [ $((cnt_pass + cnt_skip)) -eq ${cnt_total} ]; then + exit "$KSFT_PASS" +else + exit "$KSFT_FAIL" +fi |
