触控板手势协议

在 0.50.0 版本加入.

触控板不仅能滚动:系统还会识别捏合、旋转、多指轻扫、双指双击和手指静止按住,并将它们作为手势交给应用。触控板手势协议是将这些信息交给终端内程序的开放扩展。与其他扩展一样,它小巧、低干扰、易于实现,不改变 TTY 的本质。

本协议独立于 数位板指针协议,后者直接报告手指接触,由程序自行识别手势。两者可同时启用,程序可按需选择。

消息封装

帧使用带有 gesture 选择器的 OSC 777::

<OSC> 777 ; gesture ; v=1 : op=query : id=42 <ST>

帧遵循 OSC 777 扩展封装 规定的 OSC 777 共用规则:字段语法、拒绝哪些帧、单位和时钟。协议版本 v 当前为 1,op 为 query、cap、mode 或 event。

能力探测

程序询问终端是否支持本协议::

<OSC> 777 ; gesture ; v=1 : op=query : id=42 <ST>

id 是程序自行选择的非负 31 位整数,回复原样返回,以便匹配查询。支持协议的终端回答::

<OSC> 777 ; gesture ; v=1 : op=cap : id=42 : caps=15 : mode=0 <ST>

caps 是可报告手势的位集合:捏合 1、旋转 2、轻扫 4、智能缩放 8、按住 16。它描述终端能从当前窗口系统转发什么,而非是否已连接触控板。mode 为下述模式的当前状态。不支持本协议的终端会忽略查询,因此程序必须设置超时。查询不改变模式。

模式

报告默认关闭,按终端窗口开启::

<OSC> 777 ; gesture ; v=1 : op=mode : mode=1 <ST>

mode 是程序请求的手势种类位集合:1 表示移动手指形成的捏合、旋转、轻扫和智能缩放,2 表示按住;0 关闭报告。相应位开启后,窗口上发生的对应手势由本协议报告。终端会舍弃不认识的位,因此依赖某个位的程序应读回状态。同一手指产生的滚动和触摸事件不受影响;系统在双指捏合期间产生滚动时仍会滚动,因此通过捏合缩放的程序也应预期滚动报告,并在捏合期间忽略它们。

模式仅作用于当前窗口,由 RIS 重置,修改模式会取消全部进行中的手势。程序必须在退出、挂起或失败时关闭,恢复时重新开启。多路复用器与其他终端状态一起保存它,重连时恢复,脱离连接时关闭。终端可允许用户针对全部或部分程序拒绝模式或部分位;被拒绝的位读回为关闭,不报告相应手势,因此程序应读回状态,不要假定已获准。

不带 mode 字段的 mode 请求读取状态而不修改,回复为::

<OSC> 777 ; gesture ; v=1 : op=mode : mode=1 <ST>

程序重连后恢复时,应读回模式,确认是否仍然有效,而非假定状态保留。

报告

每个事件对应一帧报告::

<OSC> 777 ; gesture ; v=1 : op=event : kind=1 : phase=2 : t=408211 : tu=73 : x=25600 : y=51200 : mods=0 : scale=120 : total=1340 <ST>

始终包含 kind、phase、t、x、y 和 mods:

kind

1 捏合,2 旋转,3 轻扫,4 智能缩放,5 按住

phase

1 开始,2 更新,3 结束,4 取消

t

系统认定事件发生的时刻,以终端全部扩展共用时钟的毫秒表示,见 OSC 777 扩展封装。终端自行生成的取消事件使用其生成时刻

tu

t 之后的微秒数,范围 0 至 999;仅在系统时间戳精度高于毫秒时出现

x、y

指针位置,以物理像素的 1/256 为单位,有符号、从零开始,相对于字符单元区域左上角,范围为 ±1073741823。物理像素指终端帧缓冲区的像素,而非窗口系统缩放后的逻辑单位

mods

Shift 1,Alt 2,Control 4,Super 8

各手势新增的字段:

捏合 scale

自上次报告以来的缩放变化,单位千分之一:100 表示放大百分之十,-100 表示缩小百分之十

捏合 total

自手势开始以来的总缩放比例,单位千分之一,不变时为 1000

旋转 angle

自上次报告以来的旋转量,单位百分之一度,顺时针为正

旋转 total

自手势开始以来的总旋转量,单位百分之一度

轻扫 dx、dy

手指移动方向,各轴为 -1、0 或 1,向右和向下为正。两个轴中恰有一个非零

智能缩放和按住不携带额外字段。

任意手势还可携带 fingers,表示参与的手指数。系统在手势开始时提供该值,此后该手势的每条报告均包含它;系统未提供时省略。

生命周期

手势以 begin 开始,接任意数量 update,以 end 或 cancel 结束。begin 中的 scale 为 0,angle 为 0,total 为 1000 或 0。轻扫和智能缩放为瞬时手势:先报告 begin,紧接字段相同的 end。捏合与旋转相互独立,通常同时发生,因为捏合的双指也会略微旋转,因此关注其中一种的程序必须能处理另一种报告。

手指在触控板上静止时开始按住,抬起时结束,中间没有 update。手指开始移动时取消按住,通常继而开始滚动或其他手势。程序可用它在用户接触触控板的一刻停止运动,例如停止惯性滚动。

手势由其开始时所在的窗口捕获,即使指针移出,该手势后续报告仍发给原窗口,因此坐标可为负数或超过字符单元区域。模式变化或终端重置时,终端取消进行中的手势。取消后不再报告该手势的任何后续事件,包括 end。程序必须容忍重复取消,以及对仍处于活动状态的同种手势收到新 begin,后者替代旧手势。

无法跟上程序处理速度的终端或中继,只能将同一手势的连续 update 合并:捏合报告的 scale 为缩放变化的乘积,旋转报告的 angle 为角度之和,两者的 total、t 和 tu 都取最后一条报告。begin、end 和 cancel 绝不合并或丢弃,见 OSC 777 扩展封装。

多路复用器按窗格原点(含边框)转换 x,y,其他字段原样转发;每个手势仅交给其开始时所在窗格。窗格移动或调整大小时取消手势。

版本与扩展

程序忽略不认识 op 的帧、不认识 kind 的报告和不认识的字段,因此三者新增内容均不必升级版本。新手势种类也只发送给通过其独立模式位请求它的程序,按住即如此;所以无论协议如何增长,发送 mode=1 的程序只看到该位定义的四种手势。新增 phase 值或改变既有字段含义时,需要新版本。

实现情况

  • silkie(此 kitty 分支):macOS 支持捏合、旋转、轻扫和智能缩放;Wayland 支持捏合、旋转、轻扫和按住,并报告手指数;X11 支持捏合、旋转和轻扫,并报告手指数。kitty --debug-input 记录所见手势。

silkie 实现说明

silkie 转发窗口系统识别的手势:

  • macOS:使用 AppKit 的 magnify、rotate、swipe 和 smart magnify 事件。系统设置中将三指轻扫配置为页面间轻扫时,应用才能收到它;启用触控板智能缩放时才能收到智能缩放。AppKit 不提供手指数,因此省略 fingers。事件时间戳精确到微秒,所以仅 macOS 发送 tu;Wayland 和 X11 手势时间戳为整毫秒。

  • Wayland:通过 pointer-gestures-unstable-v1 第 3 版提供;不支持的合成器上 caps 为零。一个捏合事件同时包含缩放与旋转,因此报告为一个捏合手势和一个旋转手势。轻扫结束后按主方向报告,合成器通常保留大多数三指轻扫供自己使用。按住也会停止 kitty 自身的惯性滚动。

  • X11:通过 XInput 2.4 的触控板手势提供,需要 X 服务器 21.1 或更新版本,以及会发送这些事件的驱动(如 libinput 驱动)。服务器不支持时 caps 为零。捏合和轻扫的报告方式与 Wayland 相同。X11 不提供按住或智能缩放。