Skip to content

第 10 章:输入后端——SDK、UHID 与 AOA 的三种世界

scrcpy 不把“键盘控制”绑定为一种实现。processor 接口先统一内部事件,再把注入策略延迟到运行时选择:语义事件、虚拟硬件或真实 USB accessory HID。

一、三个 processor 接口

客户端定义:

  • sc_key_processorprocess_key 必选,process_text 可选,并声明 async_pastehid
  • sc_mouse_processor:motion/click 必选,scroll/touch 可选,并声明是否 relative mode;
  • sc_gamepad_processor:设备增删、axis、button。

接口定义见 key_processor.hmouse_processor.hgamepad_processor.h

input_manager 只持有这三个接口指针,所以快捷键、窗口坐标与 clipboard 逻辑可被所有后端复用。

二、三条路径对照

后端数据路径设备看到什么依赖典型优势
SDKcontrol socket → InputManager注入的 Android InputEventADB control文本注入、绝对触摸、配置简单
UHIDcontrol socket → /dev/uhid虚拟物理 HID 设备Android/Linux UHID键盘布局自然、相对鼠标、手柄
AOAlibusb control transferUSB accessory HID物理 USB + libusb输入绕过 Android server/control socket

后端选择与实例装配集中在 scrcpy.c 591–751 行

三、SDK:发送“动作语义”

keyboard_sdk 把宿主按键映射为 Android keycode/meta state,然后构造 INJECT_KEYCODEINJECT_TEXTmouse_sdk 构造 touch/scroll 控制消息。设备端再创建 KeyEvent/MotionEvent 调系统服务。

优点:

  • 可指定 display id,适合多显示与虚拟显示;
  • 可直接注入 UTF-8 文本;
  • 支持绝对触摸、面板与系统动作;
  • 不需要注册虚拟硬件。

限制:

  • 键位解释受 Android keycode 与 KeyCharacterMap 影响;
  • 某些设备要求额外“USB debugging (Security Settings)”;
  • 鼠标更像触摸模拟,而非完整硬件鼠标。

SDK 键盘 ops 装配见 keyboard_sdk.c

四、UHID:发送“硬件 report”

UHID 路径不发送 Android keycode,而发送三类控制消息:

  1. UHID_CREATE:id、vendor/product、设备名、report descriptor;
  2. UHID_INPUT:一帧 HID input report;
  3. UHID_DESTROY:关闭虚拟设备。

设备端 UhidManager 打开 /dev/uhid,写 create2/input/destroy 事件,并监听 output report,见 UhidManager.open()

Android 因而把输入看成真实连接的键盘、鼠标或手柄:键盘布局由系统物理键盘设置处理,相对鼠标能触发 pointer capture,游戏也能收到标准 gamepad。

output report 为什么要返回桌面

物理键盘会收到 LED 状态(Caps Lock 等)output report。设备端监听 UHID fd 后把 report 封成反向消息,客户端 uhid_devices 再更新本地 HID 状态。控制通道因此不仅“输入设备”,还模拟了硬件的双向反馈。

五、AOA:绕过 server 的 USB HID

Android Open Accessory 2.0 定义了 accessory 侧注册 HID 的控制请求。scrcpy 通过 libusb:

  1. ACCESSORY_REGISTER_HID 注册 id 与 descriptor 总长;
  2. 可能分片发送 ACCESSORY_SET_HID_REPORT_DESC
  3. ACCESSORY_SEND_HID_EVENT 发送 report;
  4. 关闭时 unregister。

请求常量和实现见 aoa_hid.csc_aoa_register_hid

这条路径完全不经过 control socket 的输入发送,甚至可用于 scrcpy --otg 的无投屏纯控制模式。但设备必须通过 USB 物理连接,平台构建也要启用 libusb。

六、共享 HID 层避免三份状态机

app/src/hid 维护 report descriptor、按键 bitmap、modifier、鼠标轴/button、gamepad axis/button 等通用状态。UHID 与 AOA 只负责“如何发送 open/input/close”,不各自重新解释 SDL event。

例如 keyboard backend 最终都消费共享 sc_hid_input;改变一个按键时根据当前完整键盘状态生成 report。这样按键同时按下、repeat、release 和 LED 状态不会因 transport 不同出现分叉。

七、relative mouse 为何改变 UI 行为

UHID/AOA 鼠标通常是相对位移:report 只说 dx/dy,不带屏幕绝对位置。因此 mouse_processor.relative_mode=true,screen 必须捕获鼠标、隐藏宿主光标,并在窗口焦点/快捷键时正确释放。

SDK 鼠标则需要当前窗口坐标映射到视频坐标,属于绝对模式。一个接口字段就让 UI 根据后端切换交互模型,而不用判断具体类型。

八、HID paste 的同步难题

HID 没有“注入字符串”,Ctrl+V 是最可靠的多语言文本输入方式:

  1. 把电脑 clipboard 发给设备;
  2. 等设备 ACK;
  3. 再发送 HID Ctrl+V。

key_processor.async_paste 告诉 input manager 该后端必须等待 ACK。SDK 后端可在同一 control handler 内 set clipboard 后调用 PASTE,不需要外部等待。

UHID 键盘等待逻辑可见 keyboard_uhid.c,接口上的语义注释见 key_processor.h

九、选型不是“越底层越好”

场景推荐原因
常规手机触控与文本SDK支持绝对位置、文本和显示 id
桌面模式/游戏的真实鼠标UHID相对鼠标与硬件语义
游戏手柄UHID 或 AOA系统识别标准 gamepad
只有 USB 控制、不需要镜像AOA不依赖 server 媒体链路
无线 ADBSDK/UHIDAOA 需要物理 USB
复杂非 ASCII 文本SDK clipboard/pasteHID 逐键受布局影响

十、扩展一个后端需要改哪里

一个新 input transport 通常需要:

  1. 实现相应 processor ops;
  2. 复用或扩展 hid 状态机;
  3. 在 options 增加枚举与校验;
  4. scrcpy.c 组合根创建具体对象并赋给 kp/mp/gp
  5. 定义生命周期 start/stop/destroy;
  6. 若跨 control socket,双端扩展消息与测试。

input manager 原则上无需修改,除非新后端需要新的内部事件语义。

十一、本章小结

SDK、UHID、AOA 不是同一路径的三个参数,而是三个不同抽象层级:操作系统事件、虚拟硬件、USB 硬件。processor 接口让 UI 与这些差异隔离,共享 HID 层又避免状态机重复。下一章回到 screen,解释窗口中的一个坐标如何经过 letterbox、旋转、镜像和 session 尺寸后准确落到 Android display。

基于 scrcpy 4.1 源码的独立学习笔记