返回日报条目

资料节译

修正 systemd-hwdb 元数据后,Krita检测到 VEIKK A15 Pro 触控笔输入

阅读原文

我决定暂时离开科技领域,尝试一些新东西,比如数字绘画。于是我从柜子底部翻出了绘图板(它在那里积灰好几年了)。

果不其然,我的 Linux 配置一开始就拒绝识别它。

我想用 Krita 画画,于是通过 USB 把绘图板连接到笔记本电脑。绘图板上的指示灯亮了起来,我天真地以为这是个好兆头。于是我拿起手写笔开始画。

瞬间失望。

我在绘图板上用手写笔做的任何操作,都没有反映到显示器上。我想到故障可能出在两个环节之一。

  1. Linux 没有检测到绘图板。
  2. 绘图板的输入没有被检测到。

我通过 lsusb确认了第一种情况,并清楚地看到:

Bus 003 Device 006: ID 2feb:0006 Beijing Veikk E-Commerce Co., Ltd. A15PRO

这样就把问题缩小到了第二种情况。

为了确认调试方向正确,我追踪了手写笔输入的路径:从笔尖接触绘图板开始,到输入最终在 Krita 中呈现为止。

下面有些细节可能只适用于我的系统,配置如下:

操作系统:Arch Linux 内核:7.1.5-zen1-1-zen libinput:1.31.3 sway 版本:1.12 绘图板:VEIKK A15 Pro

下面说说它应该如何工作:

那么,我们就逐层调试这个错误。

首先,检查 USB HID 设备是否绑定到了内核的 usbhid 驱动。

要检查任何内容,首先需要找到存放手写笔输入设备文件的位置(Linux 中一切皆文件!)。因此,我们用下面的命令检查 Linux 当前检测到的所有输入设备:

cat /proc/bus/input/devices

我找到了 Linux 能看到的所有输入设备的信息,其中包括手写笔:

这表明输入手写笔的处理程序是“event19”。

那么,如何访问与这个特定输入处理程序相关的文件呢?

Linux 有一个名为 /dev 的设备文件系统。内核暴露硬件接口时,会在其中创建设备节点。对于输入设备,内核的 evdev 使用以下命名约定:

/dev/input/event19

接下来,我用下面的命令验证 usbhid:

udevadm info /dev/input/event19

结果如下:

ID_USB_DRIVER=usbhid

由此确认,USB HID 设备已绑定到内核的 usbhid 驱动。

接下来是位于 /sys/class/input/event19/device/device/report_descriptor 的 HID 描述符。

绘图板的 HID 描述符实际上告诉 Linux 这是支笔,并描述了 X、Y、压力、按键等信息。

接下来是 evdev 层,我们用下面的命令检查:

sudo evtest /dev/input/event19

运行此命令后,移动手写笔会产生不断变化的 X、Y 和压力事件。

因此,直到这一层为止,我们都能清楚地收到输入。

接下来是 libinput 层。我们要检查 libinput 是否将它识别为可用的绘图板。

为此,我们运行了:

sudo libinput list-devices

运行后,终端输出了:

VEIKK.INC A15PRO Stylus:
    libinput bug: missing tablet capabilities: resolution.
    Ignoring this device.

现在我们找到了问题所在。

Libinput 需要某些元数据,如果缺少这些信息,就会忽略设备。本例中缺少的是“resolution”(分辨率)。

在此语境下,轴分辨率指每毫米的物理移动对应多少坐标单位。例如,分辨率为 129 单位/毫米,意味着手写笔在绘图板上移动 1 毫米,对应 ABS_X 坐标范围内大约 129 个单位。

那么,分辨率去哪儿了?

我们再回头看看之前的报告描述符,因为所有元数据最初都来自那里。

再看一遍,似乎有些地方不对劲。

“Unit = 0x33, Unit Exponent = -3”是什么意思?

hid-decode 将其解读为:

Unit (EnglishLinear: in³)

所以绘图板用立方单位测量 X 和 Y?这听起来不太对。

0x33 是一个打包的 HID 单位值。低位半字节描述单位系统,后续位则编码各维度的指数。在这种编码中,0x13 表示英寸,而 0x33 表示长度指数为 3 的英寸,hid-decode 将其显示为 EnglishLinear: in³。

这对 X/Y 坐标来说很可疑:X 坐标是长度,因此立方单位在这里毫无道理。HID 规范在 6.2.2.7 节定义了这种单位编码,内核源码引用的也是这一节。

但关键问题不只是 0x33 看起来奇怪。Linux 内核实际上会如何处理它?

我们怎么会因此得到没有分辨率的结果?

为此,我们需要看看 Linux 如何根据这个 HID 描述符计算分辨率。

查看内核源文件(https://github.com/torvalds/linux/blob/d89f04ac41e3b83a25b02c8d818ce09d7afc74c9/drivers/hid/hid-input.c):

hidinput_calc_abs_res() - 此函数负责根据 HID 描述符提供的信息计算绝对输入轴的分辨率。

内核要求 X/Y 单位必须是它支持的长度表示形式之一。我的绘图板报告的单位是 0x33,hid-decode 将其解读为 EnglishLinear: in³。内核的 X/Y 分辨率计算不接受这种单位表示形式,因此计算结果为零。

switch (code) {
    case ABS_X:
    case ABS_Y:
    case ABS_Z:
    case ABS_MT_POSITION_X:
    case ABS_MT_POSITION_Y:
    case ABS_MT_TOOL_X:
    case ABS_MT_TOOL_Y:
    case ABS_MT_TOUCH_MAJOR:
    case ABS_MT_TOUCH_MINOR:
        if (field->unit == 0x11) {      /* If centimeters */
            /* Convert to millimeters */
            unit_exponent += 1;
        } else if (field->unit == 0x13) {   /* If inches */
            /* Convert to millimeters */
            prev = physical_extents;
            physical_extents *= 254;
            if (physical_extents < prev)
                return 0;
            unit_exponent -= 1;
        } else {
            return 0;
        }
        break;
}

我们可以用下面的命令交叉验证 Linux 看到的分辨率:

sudo evemu-describe /dev/input/event19

结果如下:

(原图获取失败,本处不展示图片。)

两个轴的分辨率都报告为 0,这证实了我们的追踪结果。那么,该如何修复?

一种常用解决方案是另找一种途径,绕过 libinput 向 Krita 应用程序提供输入。我在这里使用 OpenTabletDriver 作为替代方案。

OTD 直接与绘图板通信,并通过自身的绘图板支持来解析其报告。

我成功安装了驱动,问题也因此解决。

不过,如果我们直接向用户空间提供修正后的设备元数据,会怎么样?

我可以从这里提供的硬件规格中获取正确的分辨率:https://veikk.com/en-in/products/creator-a15pro

我们可以根据官方给出的 10x6 英寸有效区域(254 x 152.4 毫米)以及 0-32767 的逻辑范围来设置分辨率(逻辑范围来自描述符):

X:32767 / 254 = 129 单位/毫米

Y:32767 / 152.4 = 215 单位/毫米

设备出现时,如何向 libinput 提供这些元数据?

通常作为第一个用户空间进程(PID 1)运行的 systemd,包含一个硬件专用元数据修正数据库,udev 会在设备出现时将其中的信息应用到设备上。这个数据库叫作 systemd-hwdb。

于是我创建了这个文件的本地版本:/etc/udev/hwdb.d/61-evdev-local.hwdb,并在其中添加了以下内容:

evdev:input:b0003v2FEBp0006*
    EVDEV_ABS_00=::129
    EVDEV_ABS_01=::215

各部分的含义如下:

evdev:input: - 匹配一个 evdev 输入设备

b0003 - USB 总线

v2FEB - VEIKK 厂商 ID

p0006 - A15 Pro 产品 ID

* - 其余 modalias 字段可以是任意值

EVDEV_ABS_00=::129 表示最小值和最大值保持不变,分辨率设为 129。

然后我重建并重新加载了数据库:

sudo systemd-hwdb update
sudo udevadm trigger /dev/input/event19

完成这些操作后,Krita 就能检测到手写笔输入。分辨率的变化可以在 udev 层看到,libinput 也不再抛出之前的错误。

确认它在我的机器上正常工作后,我向 systemd 提交了包含相同条目的拉取请求。该请求已合并:https://github.com/systemd/systemd/pull/43962.

因此,如果你有 VEIKK A15 Pro,那么只要包含此修复的 systemd 版本发布,你就不需要做这些操作了。

回头看,问题本身比排查过程简单得多。绘图板的描述符将 X/Y 轴声明为内核不接受的单位,因此分辨率变成了零,libinput 也就拒绝了这个设备。这甚至不是改一个字节就能解决的。描述符还给两个轴都设置了相同的物理最大值 8000;即使单位修正了,那也会对应一个边长为 8 英寸的正方形,而实际绘画区域是 10Ã6。因此我不能只修补单位,而必须从规格表中查出真实尺寸。

如果你的另一款绘图板也有相同症状

通用处理步骤如下:

用 lsusb 获取厂商 ID 和产品 ID。

从制造商的规格中找到真实的有效区域尺寸,单位为毫米。

将每个轴的逻辑最大值(来自 evtest 或描述符)除以该轴的毫米尺寸。这就是以单位/毫米表示的分辨率。

在匹配设备的 hwdb 条目中填入这些数值,然后运行 sudo systemd-hwdb update 并触发设备。

我仍然不知道 Windows 为什么从未遇到这个问题。它可能会容忍这个描述符,也可能是 VEIKK 自己的驱动绕过了问题。我没有测试过是哪一种,所以就把它留作一个疑问。

这块在柜子底部待了好几年的绘图板表现还不错。现在我只需要学会怎么画画了。