---
title: Linux处理多触屏的终极解决方案
date: 2021-08-24T00:00:00+08:00
lastmod: 2021-09-14T00:00:00+08:00
draft: false
description: "Linux with mutli touch screen"
authors:
  - Kushidou
tags:
  - Linux
  - Deepin/UOS
  - 触摸屏
categories: []
toc: true
comments: true
---

一般Linux操作系统接入多个触摸屏的最终解！

<!--more-->

从[21年3月中旬开始](https://www.small09.top/posts/210311-multitouchscreeninlinux/)，我就在不断探索Linux操作系统下接入多个触摸屏且为拓展显示的解决方案，随着我们遇到的问题不断变得复杂，那篇博客也越来越乱越来越难读。直到今天我把里面最复杂的方案尝试成功后，我感觉我已经获得了Linux触摸屏的终极奥义。

*重要：* 本文全文基于软件`xinput`

*更重要：* 如简介所说，仍有一种方法无法对应，具体为`【两个触摸屏的 VID:PID 与 设备描述 完全一致时，即两个完全相同的显示器】`，本文内容对这种情况仍然是无解的，个人认为只能通过硬件端口(/sys/class)来进行识别与调整，本文不涉及该方法(还没吃透)。

## 0、核心：xinput

xinput是Linux下的一款配置测试X输入设备的神器，具有以下功能（我们将会用到的）

> xinput (list)  :  直接键入xinput或者跟上list参数，就会列出本机所有输入设备，包括名称、ID、层级
>
> xinput list --id-only  :  仅仅列出设备ID（序号，每次开机都可能改变）
>
> xinput map-to-output  :  将输入设备映射到输出，比如将触屏映射到某个屏幕
>
> xinput list-props  :  列出某个设备的详细信息，后接参数为设备ID。

## 1、简约：两个完全不同的触摸显示器

`xinput map-to-output "$DEVICE NAME A" PORT-A`

这里对应了两种情况，一是VID:PDI和设备描述符都不同，即毫无关系的两款；二是具有相同的VID:PID，但是设备描述符，这一般是同款芯片配了不同的固件。

首先通过xinput查看所有的输入设备：

```shell
~$ xinput
┌ Virtual core pointer		id=2 [master pointer  (3)]
|	ILITEK TLITEK-TP		id=9 [slave  pointer  (2)]
|	ILITEK Multi-Touch		id=7 [slave  pointer  (2)]
(下略)
```

可以看到`ILITEK ILITEK-TP`和`ILITEK Multi-Touch`显示为两个不同名称的设备，那么不论其VID:PID具体如何，我们都可以按如下方式进行映射：

`xinput map-to-output "ILITEK ILITEK-TP" HDMI-0`

`xinput map-to-output "ILITEK Multi-Touch" HDMI-1`

这种方式的要点是1.必须带英文`""`，2.引号内名称须完整。`map-to-output`所携带的第一个参数为输入设备，不一定要设备id，使用设备名也是能够实现。

## 2、复杂但通用：VID:PID不同的显示器

不论usb设备还是PCI设备，他们都有一个唯一识别符VID:PID，这相当于设备的身份证，同功能的设备，不同的厂商、不同的型号，就会有不同的ID。我们可以通过指定设备的VID:PID，筛选出输入设备的编号id，进行映射。

通过`xinput list-props [id]`可以得到某个设备的详细信息，其中就有十进制的VID,PID，如果这两个参数可以和指定的对应上，那么我们可以提取id映射到屏幕。`list-props`的结果示例如下：

```shell
(后续再补)
```

其中`Device Product ID`一项有两个值，但不像是16进制的VID:PID，经过换算，他们其实是十进制的。利用grep+awk指令筛选出这两个值，和预先设定的比较，确定设备的id。写出来就是这样子：

```bash
#!/bin/bash
# 先通过lsusb/xinput/xrandr 等命令确定如下硬件信息:
# 1、屏幕VID:PID(16进制)和显示接口，多屏重复本段
VIDA=222a
PIDA=0001
PORTA=VGA-0

# 2、先修算出10进制。如果xinput list-props [n] 的“Device Product ID”为16进制的，注释这2行
# 或者这里直接填写Device Product ID里面的结果，先VID后PID。
# 字母O，不是数字0，多屏重复本段
VIDA_O=$((16#$VIDA))
PIDA_O=$((16#$PIDA))

# =========自动处理步骤，请勿随意修改==========
for I in $(xinput list --id-only)
do
# 根据设备ID取其序号
    CUR_FIRST=$(xinput list-props $I|grep "Device Product ID"|awk -F : '{print $2}'|awk -F , '{print $1}'|awk '{print $1}')
    CUR_SECOND=$(xinput list-props $I|grep "Device Product ID"|awk -F : '{print $2}'|awk -F , '{print $2}'|awk '{print $1}')
# 3、屏映射，多屏重复本段
    if [ "$VIDA_O" == "$CUR_FIRST" ] && [ "$PIDA_O" == "$CUR_SECOND" ];
    then
		  xinput map-to-output $I $PORTA
    fi
done
exit 0
```

这个脚本使用`xinput list --id-only`仅列出了设备的id序号，然后轮询所有的设备找出符合要求的编号，最后仍然使用`map-to-output`进行映射。这个脚本易于扩展，如果多个触摸屏只需要将3个有序号的注释后面的一块内容重复、修改名字就可以了。

## 3、[2]的进阶版

前面都是通过shell/bash脚本的方式实现的重映射，如果要自动映射还需要添加自启动。如果设备进行了插拔，这个设备的映射就失效了，需要手动重新跑一跑脚本。我尝试了编写udev-rules文件，当触摸的设备发生变化时就延时启动映射脚本，但无一例外都是失败的，我觉得是udev生效的必xinput更早，无论延时多久都是这样。

在银河麒麟操作系统中我发现了它提供的触摸映射方法，使用的是`pyudev`库，采用第三方的方法实现udev-rules的效果，而且在这里延时不会影响系统的加载顺序，只需要系统额外安装`python3+pyudev`，并且使用desktop文件在开机时启动，即可。

```python
#!/usr/bin/python3
# -- coding: UTF-8 --
# -- Author: Blues --

import pyudev
import os

context = pyudev.Context()
monitor = pyudev.Monitor.from_netlink(context)
monitor.filter_by(subsystem='hid')
for device in iter(monitor.poll, None):
    if device.driver != "hid-multitouch":
        continue
    if device.action == "add" or device.action == "change":
        os.system("/var/my-touchscreen/remap-screen")

```

上面直接贴了麒麟自带的代码，就修改了一下脚本的路径，经过多次验证，在uos下可以正常使用。借此机会我也参照麒麟的代码修改了一下，用户不用看命令行那些多余的东西，只需要点击几个GUI窗口，程序就可以按照*设备名、VID、UID、物理接口*四大特征，自动映射到对应的显示屏上了。
