RFC-0096 - 用户输入架构

RFC-0096:用户输入架构
状态已接受
区域
  • HCI
说明

用于在具有图形界面和多个运行时的系统上向 Fuchsia 传递用户输入事件(键盘、鼠标、触摸等)的高级架构。

问题
Gerrit 更改
作者
审核人
提交日期(年-月-日)2021-04-15
审核日期(年-月-日)2021-05-26

摘要

本文 RFC 介绍了在具有图形界面和多个运行时的系统上,在 Fuchsia 上传递用户输入事件(键盘、鼠标、触摸等)的目标高级架构。用户可以通过多种方法/设备(包括键盘、鼠标、触控和按钮)向系统提供输入。本文档介绍了 Fuchsia 上的输入事件如何从驱动程序级原始数据转变为分派给用户空间软件(例如 Flutter 应用)的输入事件。本文中所述的组件在发布时处于开发阶段。

设计初衷

为了支持 Fuchsia 的包容性理念,Fuchsia 输入必须为基于不同运行时构建的组件提供平台级支持,这些运行时利用不同的界面框架(例如 Flutter、Chromium),同时还允许产品所有者自定义输入行为。在其他平台上,这些行为通常内置于特定界面框架的实现中。Fuchsia 带来了一些独特的挑战,需要将平台输入处理与特定界面框架的具体细节分离开来。

在 Fuchsia 代码中,目前有多个输入路由路径(根呈现程序输入流水线),但关于首选路径和迁移计划的公开指南有限。此 RFC 概述了 Fuchsia 上用户输入路由的目标架构,并提供了有关如何扩展输入处理以适应未来用例的指南。

要求

  • 安全性:借助 Fuchsia 输入堆栈,您可以轻松构建安全的产品。
    • 输入事件可能包含密码和付款数据等敏感数据,以及与用户活跃时间相关的信息。所有用户输入事件都应被视为个人身份信息,并且只能由可信的系统组件分派给最终用户软件。
    • 如果路由错误,用户输入内容也可能会被滥用,例如恶意界面会导致用户点击他们不打算点击的按钮(“点击盗用”)。
    • Fuchsia 平台应尽可能鼓励开发者通过提供 API 接口来构建安全的产品,以便轻松理解和审核系统中用户输入事件的流程。
  • 正确性:输入事件的传送符合用户的预期。
    • Fuchsia 输入系统负责在上下文中解读输入事件,并将其传递给当前在系统上运行的正确目标组件。不过,“正确”的定义可能会因事件和产品类型而异。
    • 即使界面正在呈现动画或更改大小,输入事件传送也应保持一致。
    • 事件有时可能会同时分派给多个组件(例如键盘快捷键)。
  • 性能:用户输入速度足够快。
    • 虽然确切的延迟时间要求因输入设备和产品类型而异,但输入传送对延迟时间特别敏感,应力求足够快,以免用户感知到延迟。
    • 输入架构应避免引入不必要的延迟。特别是,应谨慎处理进程上下文切换,以免出现不必要的阻塞调用。
  • 可自定义性:系统应针对基于 Fuchsia 平台构建的不同产品支持不同的用户输入行为。 * 具体而言,该产品应能够自定义输入行为。 不过,此 RFC 中提出的初始实现只会满足最终自定义需求的一小部分。(请参阅输入流水线自定义。)
    • 有些产品需要键盘和鼠标支持,而有些产品主要侧重于通过触摸和按钮进行互动。
    • Fuchsia 平台应提供钩子,以便针对产品自定义输入行为(例如,根据系统上下文对按钮事件进行不同的解释)。
    • Fuchsia 应允许产品根据需要将输入事件映射到其他类型,例如将触摸事件重新解释为鼠标事件,以便与不支持触摸手势的软件兼容。
  • 可扩展性:Fuchsia 可以添加对新输入模式的支持。
    • 虽然具有截然不同的输入需求的产品可能需要对平台进行更改,但应该能够在不大幅重写现有输入堆栈的情况下添加对新输入方法的支持。

激励性示例

这些用例绝不详尽无遗,但可以让您深入了解此架构应支持的行为类型。

  • 在屏幕上显示来自多个框架的界面时,正确解读触摸屏手势。
  • 在屏幕上显示多个文本框(由不同的框架支持)时,使用应用了适当布局信息的键盘输入进行路由。
  • 除了这些按钮的常规功能外,还允许按钮组合(例如同时按下音量调高和音量调低按钮)触发恢复出厂设置。
  • 在设备处于休眠或合盖状态时,抑制笔记本电脑触摸屏上的输入。
  • 解读笔记本电脑触控板手势(双指张合缩放、双指滚动)。

背景信息和术语

  • 人机接口设备 (HID) - 允许向用户输入和输出信息的设备,例如键盘、鼠标、触摸屏或消费类控制器(按钮)。通常,这类设备是指使用 USB-HID 规范的设备。
  • 输入事件 - 单个用户输入事件,例如按键按下、鼠标移动操作或轻触事件。在处理事件时,系统可能会根据当前上下文为事件添加其他信息注解。
  • 指针事件 - 与屏幕上的位置相对应的用户输入事件,例如触控事件或鼠标事件。指针事件是输入事件的子集。
  • 事件流 - 一组通常在时间上相近的相关输入事件。
  • 输入处理脚本:用于执行单个输入处理阶段的软件。输入处理脚本会将输入事件作为输入,同时也会发出输入事件。它可能会修改事件或与系统的其他部分通信。
  • 输入流水线算法 - 此算法涉及将一系列输入处理脚本串联起来,以处理 Fuchsia 输入。它充当 Fuchsia 上输入的政策层。
  • 输入流水线实现 - 输入流水线算法的实现。实际上,这是 fuchsia.git 中的一个组件,负责将驱动程序级用户输入事件路由到系统的其余部分。
  • Scenic - Fuchsia 图形引擎。Scenic 还负责路由指针事件。
  • 全局场景图 - 由 Scenic 渲染的图形内容树。
  • View - 场景图中的子空间。视图通常对应于屏幕上的某个区域,但此区域不一定是矩形。
  • ViewRef - 与特定视图关联的事件对。此属性用于在多个 Fuchsia 组件中标识视图。

架构概览

此设计概述了 Fuchsia 上用户输入事件的整体流程,但未涵盖每种输入类型的所有具体细节。以下相应部分中提供了指向特定行业设计的链接,其中介绍了许多相关细节。

从底部到顶部:dev/class/input-report -> 输入流水线 -> {scenic OR a11y manager OR IME manager OR media buttons listener} -> {Chromium OR flutter OR carnelian} -> {View 1 OR view 2 OR view3 OR view 4} 侧边:Product Session 组件 -> {input pipeline OR root scene component} Root scene 组件 -> Scenic 内部:焦点管理器、场景图(具有视觉树表示法) 内部输入流水线:绑定 -> 指针事件处理脚本 -> a11y 快捷方式处理脚本 -> 媒体按钮处理脚本 -> 各种其他处理脚本 -> IME 处理脚本 -> 回退

系统会根据一系列相互关联的规则分派事件,包括但不限于:

  • 相应事件类型的产品政策(例如,将所有音量事件分派到“设置”)
  • 事件的屏幕位置(例如轻触或鼠标)
  • 当前聚焦的视图(例如用于文本输入)

输入流水线是 Fuchsia 组件,负责管理这些规则及其互动,并将用户输入事件及其处理这些事件所需的信息路由到适当的系统服务。它实现了输入流水线算法(一个由链式输入处理脚本组成的系统,每个脚本在设备输入处理中执行一个离散步骤)。此组件充当 Fuchsia 上输入的政策层。输入流水线会通过输入驱动程序直接从驱动程序层提取事件,以及测试中的合成事件和软件生成的事件(例如来自虚拟键盘的事件)。

事件在输入流水线中传输时,会从全局的低级事件(没有语义含义,但属于敏感数据)转变为具有本地含义且经过处理的限定范围的数据,最终用户软件可以对这些数据进行解读(手势、字符等)。驱动程序级输入事件大致分为两类:

  • 与屏幕上的特定位置相对应的指针事件。
  • 按钮和开关事件。

我们日后可能会扩大此范围。USB-HID 用途表内容丰富且包含众多用途,其中包括飞行模拟器、健身器械和虚拟现实设备的页面。

驱动程序级输入事件可能会转换为:

  • 触摸手势。
  • 鼠标事件(包括滚动)。
  • 文本输入事件。
  • 语义无障碍功能操作。
  • 按钮事件(例如音量调节、开启/关闭摄像头)。

通常,每个事件都会按如下方式分派:

Driver -> Input Pipeline -> UI System Component -> UI Framework-> UI View
  • 输入管道组件包含多个内部阶段,称为输入处理脚本。输入流水线充当输入的政策层,可能因产品类型而异。(请参阅下文中的输入流水线自定义。)
  • 界面系统组件包括 Scenic、无障碍功能管理器和 IME 管理器,但随着我们向平台添加功能,这些组件可能会随时间推移而扩展。这些组件是 Fuchsia 平台的一部分,位于 fuchsia.git 中。这些组件与产品无关。
  • 界面系统组件负责确定哪个/哪些视图应接收事件,并将此信息传递给界面框架运行程序以进行调度。界面系统组件使用 Scenic 中的信息(通常是焦点链)来确定哪个 View 应接收事件。
  • 界面系统组件也可能会使用事件。
  • 界面框架目前包括 Flutter、Chromium 和 Carnelian。我们预计将来会支持更多界面框架/运行时。这些 API 可用于各种类型的产品,但不是 Fuchsia 平台的一部分。
  • 界面视图是拥有对应屏幕区域(称为视图)的图形组件。(请参阅下文中的视图和事件路由。)
  • 媒体按钮事件并不总是遵循此模式。媒体按钮的最终目的地可能是设置服务,而不是界面视图。

在某些情况下,活动可能需要经过其他阶段才能最终提交。例如,系统可能会通过 Scenic 将触摸事件分派给虚拟键盘组件,后者会生成合成按键按下事件,然后通过输入流水线分派该事件。

输入流水线会将指针事件发送到 Scenic,后者负责将事件分派给正确的运行时实例。这样,Scenic 就可以始终保持对屏幕上各个元素位置的全球一致理解,并避免动画期间出现竞态条件。(请参阅下文中的路由图形事件。)

设计

输入事件的来源

输入事件通常通过输入驱动程序或通过充当输入设备控制器的 Fuchsia 组件(例如蓝牙 HID 设备的 bt-host 组件)进入系统。在许多情况下,这些事件最初都是人机接口设备 (HID) 事件,但也有一些例外情况。驱动程序和控制器会将每个事件作为 fuchsia.ui.input/InputReport 发出。通常,输入报告会被输入流水线使用和转换。不过,在恢复模式下,这些命令会直接发送到使用 Carnelian 实现的终端组件,而无需任何中间处理。

用于测试的输入事件的生成方式有所不同。(请参阅下文中的测试部分。)

输入管道

输入流水线组件是 Fuchsia 系统组件。它实现了输入流水线算法。输入流水线组件的作用类似于输入堆栈中的政策层。它负责确定特定设备支持哪些事件类型以及应如何调度输入事件,并负责管理输入设备。

输入流水线算法由以下部分组成:

  • 绑定阶段,用于将原始 InputReport 流(不包含系统状态)转换为 InputEvent 流(可能包含系统状态),以便通过流水线传递。 InputEvents
  • 一系列可能修改和/或使用这些 InputEvent(可能通过将其分派给其他组件)的 InputHandlers
  • (可选)用于处理未处理的 InputEvent 的回退阶段。

绑定阶段通常会使用有关设备状态的信息来增强 InputReport。示例:

  • 包含相对鼠标移动的 InputReport 会变为具有屏幕相对坐标的 InputEvent。
  • 按键输入 InputReport 流会转换为“按下键”和“松开键”InputEvent 流。(驱动程序通常只报告给定时间哪些按键处于按下状态,而不是分别报告按下和松开事件。)系统可能会根据后续 InputReport 中没有键来推断“键盘按键松开”事件。

理论上,产品对输入流水线的使用可以由 Fuchsia 产品配置 不过,输入流水线不会在会话领域内运行。输入流水线比会话更高级别的组件,并且会使用我们目前不希望向会话组件公开的多项功能。

输入流水线实现会作为 Fuchsia 平台的一部分提供给产品所有者。在本 RFC 发布之时,这些实现是用 Rust 编写的,并且输入处理程序是实现共享 InputHandler Rust trait 的类,不过这可能会在未来发生变化,以实现优化和可扩展性。

目前,该平台提供两种输入流水线实现:一种针对主要依赖于触摸屏互动的设备进行了优化,另一种则以配备鼠标和键盘的产品为导向。这种有限的自定义程度未来可能会扩大。(请参阅下文中的输入流水线自定义。)

输入处理程序

输入处理脚本 输入处理脚本是产品所有者自定义与产品状态相关的输入处理的主要机制。我们有时也将其称为“针对输入事件的产品政策”。输入处理程序可以

  • 通过添加上下文信息(例如,当前的键盘布局,或在点击鼠标时按住的按键)来增强事件。
  • 根据周围事件流更改事件(例如输入平滑、触控板手势)。
  • 在处理程序中处理事件,或将事件发送到界面系统组件公开的服务,后者负责将该事件分派给相应的一组界面视图。目标视图由 Scenic 中的当前视图焦点决定。(请参阅下文中的专注)。
  • 发送 OutputReports 以控制输入设备状态(例如设置 CapsLock LED)。

短期内,我们定义了一个 Rust trait InputHandler,每个输入处理程序都必须实现该 trait。从长远来看,这可能会被 FIDL 接口取代。每个处理脚本都必须能够使用输入事件,并且应输出输入事件的矢量(可能为空)。

#[async_trait]
pub trait InputHandler: Send {
    /// Returns a vector of InputEvents after handling `input_event`.
    ///
    /// # Parameters
    /// `input_event`: The InputEvent to be handled.
    async fn handle_input_event(
        &mut self,
        input_event: input_device::InputEvent,
    ) -> Vec<input_device::InputEvent>;
}

未来基于 Fuchsia 构建的产品可能需要的输入处理程序示例(有些非常奇特):

  • 无障碍功能输入处理脚本:在启用相关无障碍功能(例如开关导航或使用键盘快捷键的屏幕阅读器)时,向无障碍功能管理器发送事件。
  • 快捷方式处理程序:确定键盘事件是否与有效的键盘快捷方式匹配(可能会调用其他组件)。
  • 语言区域处理程序:为当前视图应用有关有效语言区域的信息。
  • 指针事件调度处理脚本:将触摸/鼠标/触控笔事件发送到 Scenic,以便将其调度到视图。
  • 媒体按钮处理程序:将媒体按钮路由到设置服务(或其他位置)。
  • 键盘布局处理脚本:使用当前有效的键盘布局为键盘事件添加注释。
  • 触控板手势处理脚本:处理触控板手势并转发等效的鼠标事件。
  • 输入平滑处理脚本:通过对事件求平均值来减少抖动。
  • 焦点处理脚本:使用当前聚焦的 View 的 ViewRef 为键盘事件添加注解。
  • 睡眠模式处理脚本:在设备处于休眠状态时抑制输入。
  • 多点触控 Waffle Iron Magic 按钮处理脚本:将触摸手势事件解读为媒体按钮。

在许多情况下,输入处理程序会将事件分派给 Fuchsia 系统服务,例如 Scenic、无障碍功能管理器、IME 管理器等。在这种情况下,系统会将事件标记为“已处理”,并以这种方式在整个流水线中传播。(请参阅下文中的事件流一致性)。

输入流水线自定义

通过指定要包含的处理程序及其显示顺序,可以实例化输入流水线。这样,添加处理程序、重新排列处理程序或实例化经过修改的流水线会非常轻量。采用 Rust 语言的示例:

async fn input_handlers(
 ...
 ...
) -> Vec<Box<dyn InputHandler>> {
    let mut handlers: Vec<Box<dyn InputHandler>> = vec![];
    // Shortcut needs to go before IME.
    add_shortcut_handler(&mut handlers).await;
    add_ime(&mut handlers).await;
    add_touch_handler(..., &mut handlers).await;
    add_mouse_handler(..., &mut handlers).await;
    handlers
}

2021 年,该平台提供了两个输入流水线实现,每个实现都作为 fuchsia.git 中的组件实现,能够利用未在 fuchsia SDK 中发布的特权 API。这些实现共享许多输入处理脚本(它们运行相同的代码),主要区别在于它们支持的输入模式。一种实现针对主要依赖于触摸屏互动的设备进行了优化,另一种实现则以配备鼠标和键盘的产品为导向。产品专用代码仅限于用于确定要实例化的处理程序的设置代码。

短期内,可以通过公开功能(可能采用通过会话框架中介的配置 FIDL API 的形式)向会话授予有限的可配置性,以允许会话确定其希望启用哪些输入模式。此 API 支持按产品进行配置,并会在会话启动时(即配置产品用户体验时)生效。我们不打算在正常产品运行期间支持“动态”重新配置流水线。

随着 Fuchsia 扩展以支持更多产品类别,一组输入处理程序和不同的产品配置可能会扩展,因此可能需要提供一项功能,以允许会话从各种流水线配置中进行选择,或直接配置应存在哪些输入流水线阶段以及应按什么顺序存在。某些输入处理程序可能因产品而异,并且需要位于 fuchsia.git 之外。

每款产品都应能够使用专为其特定需求量身定制的输入流水线,该流水线由该产品支持的输入行为所需的输入处理程序组成。如何具体实现此功能超出了本 RFC 的范围。理想情况下,任何此类配置解决方案都应利用平台提供的结构化配置机制,而不是发明特定于输入的内容,并且应尽量减少公开满足产品需求所需的配置界面。

视图和事件路由

Fuchsia 界面可能同时在屏幕上显示来自多个运行时的图形。界面会整理为一个由 Scenic 拥有的全局场景图,其中包含构成界面的图形内容以及呈现界面所需的信息。运行时可以通过将用户可见的内容添加到名为视图的 Scenic 资源(安装在场景图中)来贡献给场景。通常,视图对应于屏幕上的某个区域,但这些区域不一定是矩形,并且并非所有视图在给定时间都处于可见状态。由于每个视图都是 Scenic 本地资源,不适合在其他组件中引用,因此我们会将一个内核对象(称为 ViewRef)与每个视图相关联。

视图按层次结构进行组织,子视图只能影响其父视图边界内的屏幕空间,即严格的容器模型。”当某个视图是图中另一个视图的父视图时,父视图会保留对其子视图的部分控制权,尤其是与内容展示位置和事件路由相关的控制权。

视图树

下图展示了场景图的根目录结构。

根 -> 无障碍功能视图 -> sysUI -> {某个视图、某个其他视图、另一个视图}

  • 与无障碍功能管理器关联的视图可让无障碍功能管理器拦截无障碍服务所需的输入事件并更改焦点,以及在必要时在界面其余部分之上绘制。
  • “sysUI”(系统界面)视图是所有其他视图的父视图。此视图通常用于实现“系统手势”(例如在屏幕边缘滑动以切换应用)和系统键盘快捷键。它还用于界面的许多非输入方面。

路由图形事件

对于与特定屏幕位置(例如触摸、鼠标、触控笔)对应的用户事件,输入事件会先通过 Scenic 路由,然后再分派给与每个视图关联的运行时实例。这种方法的优势包括:

  • 场景图隔离:Scenic 是唯一完全了解哪个视图位于屏幕上的哪个位置的组件。我们不允许其他组件查找给定时间点屏幕上显示的内容(也称为“点击测试”)。
  • 全局一致性:由于 Scenic 是确定给定视图在特定时间在屏幕上的位置的真相来源,因此让 Scenic 分派事件可以避免在输入事件发生和传送之间出现视图更改大小、位置或消失的问题。对于用户在与设备互动时可能发生频繁变化的产品,这一点尤为重要。请注意,Scenic 还拥有焦点链,该链用于确保非指针事件的调度保持一致。
  • 并发调度(手势歧义消除):当视图重叠时,可能有多个视图对特定事件流感兴趣。例如,某产品可能希望实现“系统手势”,例如滑动关闭应用,而应用本身可能会以不同的方式解读该手势。Scenic 负责确定哪些视图应接收给定事件,并通过称为手势歧义消除的流程在这些视图之间进行协调。在此模式下,系统会将触摸事件流并行分派给多个视图,然后在“手势竞技场”中解析这些事件,以确定最终将使用事件流的视图。

专注

为了确定当前正在运行的哪个软件应接收给定的输入事件,我们需要引入视图焦点的概念。这大致是当前处于“活跃”状态且准备接收事件的视图。不过,在实践中,多个视图可能对每个事件感兴趣。

视图焦点通过焦点链(与聚焦视图及其在视图树中的祖先对应的 ViewRef 的矢量)确定。焦点链由 Scenic 拥有(因为 Scenic 管理视图),但其他组件可以请求更改当前焦点,例如响应无障碍功能操作或键盘快捷键。输入流水线负责监控焦点链中的更改,并向输入处理程序提供信息,后者可能会将事件转发给正确的客户端组件/视图。焦点链的终端元素对应于当前聚焦的视图。

有些处理脚本会对单个聚焦视图感兴趣,而有些处理脚本则会对整个焦点链感兴趣。例如:

  • 不会触发键盘快捷键的键盘事件通常会路由到当前聚焦的视图。
  • 无障碍功能管理器会使用聚焦的视图来确定屏幕阅读器启用后当前处于“活动”状态的内容。
  • 键盘快捷键管理器对整个焦点链感兴趣,其中任何一个都可能已注册键盘快捷键和关联的优先级。

焦点事件作为特殊类型的 InputEvent 在输入流水线中移动,并且与其他输入事件严格排序。

事件流一致性

事件流是一组通常在时间上相近的相关 InputEvent。例如:

  • 键盘:按下“a”键 -> 按下“a”键
  • 鼠标:悬停 -> 悬停 -> 按下按钮 -> 松开按钮
  • 轻触:手指向下 -> 移动 -> 移动 -> 移动 -> 手指向上

系统必须确保在流水线的每个阶段以及每个视图中,事件流的一致性。这意味着,如果输入处理脚本通过将事件路由到系统服务来使用事件,则应向后续输入处理脚本发送适当的“已处理”事件,以便后续输入处理脚本可以通知任何客户端。

在处理事件流时发生焦点变化时,也是如此。从客户端的角度来看,每个“按下”键事件都必须与相应的“释放”键事件匹配,如果焦点发生变化或输入设备断开连接,则必须与“取消”事件匹配。鼠标点击和触控事件流也是如此。输入流水线负责将事件标记为“已处理”,并通过输入流水线将其传播,以确保流一致性。系统服务负责在取消流式传输时通知视图。

性能

可接受的延迟时间

用户输入具有时效性。延迟时间(即从事件发生到界面响应的时间)最好尽可能短,但用户对延迟时间的容忍度因输入类型而异。在某些情况下,即使延迟时间低至 10 毫秒,用户也可能会遇到性能下降(能否完成任务)和满意度下降的情况。超过 100 毫秒,用户体验开始明显下降,超过 300 毫秒,用户体验可能无法接受。直接操作(例如在屏幕上使用触控笔绘图)对延迟特别敏感,可能需要预测事件才能提供可接受的用户体验。(如需了解背景信息,请参阅这篇延迟时间论文。)

由于本 RFC 中所述的输入系统的运行级别低于运行时和基于其构建的界面,因此用户将会遇到系统输入延迟,以及处理和呈现输入事件响应所需时间导致的任何延迟。因此,输入系统应力求尽可能快,以便为应用和运行时留出尽可能多的“延迟时间预算”。

此外,时间一致性也很重要。即使平均事件传送时间较短,事件时间的高度不稳定性也可能会降低用户体验,因此请务必查看延迟时间分布以及平均值。

提高性能

若要缩短输入架构中的延迟时间,最好的方法是尽量减少不必要的进程上下文切换。内核的每次进入和退出(通常需要运行调度程序)都会导致事件的时间出现变化。

未来,我们可能会探索在单个进程中将图形和输入堆栈中的多个组件(例如场景和输入流水线)作为单独的组件运行,以进一步减少进程跳转次数。如果发现使用 Rust 作为输入流水线的实现语言会导致额外的延迟,我们也可能会重新考虑这一选择。

为触摸事件引入手势歧义消除(也称为并行调度)功能可能会在等待感兴趣的组件响应给定事件流时引入额外的延迟。为了使此算法能够高效运行,客户端必须协同工作,并及时响应输入事件。系统需要一些机制来指定和强制执行客户端延迟时间预期 (SLA)。我们将在未来的设计中详细说明这一点。

国际化和输入上下文

每个视图都有自己的输入上下文,其中包括当前的输入法(“当前键盘”)。输入上下文不同于 fuchsia.intl.Profile 中的信息,后者包含用户的首选语言区域,并会影响界面呈现。不过,用户的语言区域设置可能会影响可用的输入法/键盘布局。如需详细了解 Fuchsia 国际化,请参阅 Fuchsia 国际化文档

系统应允许不同的视图具有不同的主动输入方法。例如,用户可以用一种语言撰写电子邮件,同时用另一种语言聊天。产品可以选择强制使用单个系统级语言区域或活跃输入法,但架构必须支持为每个视图提供不同的输入上下文。具体如何存储这些设置,我们可能会在日后决定。

除了将事件路由到正确的视图之外,输入流水线(以及 IME 管理器等关联的系统组件)在解读输入事件时,还会使用该视图的输入上下文。例如,输入流水线会使用事件发生时所处输入情境中处于活动状态的键盘布局的相关信息为实体键盘事件添加注解。与焦点更改一样,应将对有效键盘布局的更改视为输入事件,并与其他事件一起顺序处理,以避免因状态更改而出现争用条件。

无障碍

为了让所有用户(无论能力如何)都能使用 Fuchsia 设备,Fuchsia 无障碍框架提供了一些无障碍功能,可改变用户与设备互动的方式。具体而言,这有助于实现以下目标:

  • 用于“放大”界面部分或全部内容的放大镜。
  • 屏幕阅读器,可让盲人或弱视用户通过与当前界面对应的“语义树”进行探索和互动,而无需视觉输入。

启用这两项功能中的一项或两项后,无障碍功能管理器需要通过输入流程拦截输入事件,并将其解释为对当前处于活动状态的无障碍功能的命令。这些命令可以使用多种输入模式,具体取决于设备类型。例如,工作站屏幕阅读器主要使用键盘快捷键进行操作,而触摸屏设备上的屏幕阅读器可能使用一系列点按和滑动操作。根据启用的无障碍功能,无障碍功能管理器可能会决定仅使用部分事件(例如放大镜,它会使用部分手势,但允许其他手势传递到界面),或使用所有事件(例如屏幕阅读器,它会将事件转换为语义操作)。

无障碍功能管理器会与每个视图保持连接,以便通过语义 API fuchsia.accessibility.semantics 检查、描述和与该视图中的界面元素互动。例如,在启用屏幕阅读器的情况下,“双击”操作通常会作为当前所选语义节点上的语义“默认操作”传递给视图。

安全注意事项

输入流水线和相关系统组件会使用一些未在 SDK 中发布的特权 API。通过要求会话使用输入流水线来处理输入,平台能够限制外部软件可用的功能。

还应考虑界面补救攻击,例如点击劫持。恶意转移的输入事件可用于在未经用户同意的情况下授予特权(例如将点击转到恶意网站上的按钮)。虽然在平台级别很难完全防止这种情况,但输入架构必须确保仅将图形事件传递给正确的界面组件,并且产品所有者能够轻松了解输入事件在系统中的流程。

隐私注意事项

对输入流水线所做的更改应接受隐私权审核,因为攻击者可能会利用对用户输入内容的访问权限创建键盘记录器或其他恶意软件。除了恢复模式外,输入流水线应是唯一允许直接从驱动程序接收用户输入事件的组件,以防止出现这种情况。

测试

测试输入流水线

应使用与受支持的输入功能(例如触摸输入、键盘快捷键)对应的密封集成测试来验证平台输入行为,而无需考虑使用这些功能的产品代码。测试应在每个受支持的运行时中使用最少的图形组件来验证相关功能。确保功能稳定性不受特定产品的影响,对于允许开发者构建树外产品至关重要。

密封式测试会带来一些挑战,这些挑战超出了本 RFC 的范围。

测试所有其他内容

端到端测试在很大程度上依赖于合成输入事件,以便以可重现的方式模拟用户互动以进行测试。虽然大多数界面框架都包含一些用于注入事件的方法(例如 Flutter 驱动程序),但这不足以测试涉及多个运行时的任何情况。这意味着,Fuchsia 必须提供适用于创建虚假输入的 API。这通过 SL4F 和 Fuchsia 输入合成库完成,该库会通过专用注入 API 将事件插入输入流水线。此 API 应仅在开发者 build 中提供,绝不应在生产 build 中提供,因为它允许注入任意输入。

输入行业

除了本文中所述的高层次架构之外,支持每种不同类型的输入设备会增加显著的复杂性。后续的 RFC 中将介绍这些详细信息。主要输入行业包括:

  • 实体键盘(蓝牙和 USB)
  • 虚拟键盘或屏幕键盘
  • 鼠标
  • 触控
  • 触控板

文档

此 RFC 的内容应与实现详情一起添加到 Fuchsia 公开文档中。

考虑的替代方案

本部分介绍了所讨论的(广泛)替代方案中的一部分。

将根 Presenter 演变为流水线

过去,Scenic 负责调度所有用户输入事件,包括没有图形/位置组件的键盘事件。虽然键盘事件已被移除,但此属性目前仍在某些产品配置中使用。根 Presenter 中的现有输入处理代码可以扩展以处理其他用例。不过,此代码缺少测试覆盖率,需要进行大量重写才能实现所需的一致性和可配置性属性,并移除输入处理与图形 API 之间的不必要耦合。

产品专用 Scenic

由于输入(尤其是基于指针的输入)与图形密切相关,因此一个可探索的选项是通过 Fuchsia 图形引擎 Scenic 路由输入处理。这种架构将与当前状态截然不同。在此版本中,合成器从 Scenic 中分离出来,并以“立即模式”运行,这意味着它必须在窗口管理器进行任何更改时进行绘制。Scenic 会成为窗口管理器,该管理器假定为产品专用组件,每个产品类别都需要不同的实现。输入会通过此组件进行路由。

虽然这种方法在任何单个产品中都能正常运行,但需要将任何单个产品的输入自定义内容预先编译到图形引擎中。这可能意味着,每种新产品类型都需要专门的 Scenic 实现。这种方法将来可能会成为一项有价值的优化,但对于当前的用例而言,它被认为过于庞大。