RFC-0096 - 用户输入架构

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

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

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

摘要

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

设计初衷

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

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

要求

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

激发积极性的示例

这些使用情形并非详尽无遗,但可让您大致了解此架构应支持的行为类型。

  • 当屏幕上显示来自多个框架的界面时,正确解读触摸屏手势。
  • 当屏幕上显示多个文本框(由不同框架提供支持)时,路由键盘输入并应用相应的布局信息。
  • 允许通过组合按钮(例如同时按住音量调高和调低按钮)触发恢复出厂设置,而不是仅执行这些按钮的常规功能。
  • 当设备处于休眠状态或关闭状态时,禁止在笔记本电脑触摸屏上输入内容。
  • 解读笔记本电脑触控板手势(双指张合缩放、双指滚动)。

背景信息和术语

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

架构概览

此设计概述了 Fuchsia 上用户输入事件的整体流程,但未涵盖每种输入类型的具体细节。以下相应部分中链接的特定行业设计方案中介绍了许多此类详细信息。

从底部到顶部:dev/class/input-report -> 输入流水线 -> {Scenic 或无障碍管理器或 IME 管理器或媒体按钮监听器} -> {Chromium 或 Flutter 或 Carnelian} -> {视图 1 或视图 2 或视图 3 或视图 4} 侧边:产品会话组件 -> {输入流水线或根场景组件} 根场景组件 -> Scenic 内部的 Scenic:焦点管理器、场景图(包含可视化树状结构表示)输入流水线内部:绑定 -> 指针事件处理程序 -> 无障碍快捷键处理程序 -> 媒体按钮处理程序 -> 各类其他处理程序 -> IME 处理程序 -> 回退

系统会根据许多相互关联的规则(包括但不限于以下规则)调度事件:

  • 相应事件类型的产品政策(例如,所有音量事件都会调度到设置)
  • 事件的屏幕位置(例如,对于触摸或鼠标事件)
  • 当前聚焦的视图(例如,用于输入文本)

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

当事件在输入流水线中移动时,它们会从全局低级事件(没有语义,但包含敏感数据)发展为具有本地含义的范围限定的已处理数据,这些数据可由最终用户软件(手势、字符等)解释。驱动程序级输入事件大致分为两类:

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

未来可能会扩大此范围。USB-HID 用途表内容丰富,包含多种用途,包括飞行模拟器、锻炼设备和虚拟现实设备的用途。

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

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

通常,每个事件的调度方式如下:

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

在某些情况下,事件在最终传递之前可能会经历其他阶段。 例如,触摸事件可能会通过 Scenic 调度到虚拟键盘组件,该组件会生成合成按键按下事件,然后通过输入流水线调度该事件。

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

设计

输入事件的来源

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

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

输入管道

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

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

  • 一个绑定阶段,用于将原始 InputReport(不包含系统状态)的流转换为通过流水线传递的 InputEvents(可能包含系统状态)的流。
  • InputHandlers,可能通过将这些 InputEvent 调度到其他组件来完成。
  • (可选)用于处理未处理的 InputEvent 的回退阶段。

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

  • 包含相对鼠标移动的 InputReport 会变成具有屏幕相关坐标的 InputEvent。
  • 按键 InputReport 流会变成“按键按下”和“按键松开”InputEvent 流。(驱动程序通常只报告在给定时间点哪些键处于按下状态,而不是单独报告按下和松开事件。)“按键松开”可从后续 InputReport 中缺少某个按键来推断。

从理论上讲,Fuchsia 产品可以通过会话组件来配置输入流水线的使用。 <0x不过,输入流水线不会在会话 realm 内运行。输入流水线是比会话更具特权的组件,它使用许多我们目前不希望向会话组件公开的功能。

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

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

输入处理程序

输入处理程序表示一系列输入处理阶段。输入处理程序是产品所有者自定义与产品状态相关的输入处理的主要机制。我们有时也将其称为输入事件的产品政策。输入处理程序可能

  • 通过添加上下文信息(例如,有效键盘布局,或在点击鼠标的同时按下了哪些键)来扩充事件。
  • 根据周围的事件流(例如输入平滑、触控板手势)更改事件。
  • 在处理程序内处理事件,或将事件发送到界面系统组件公开的服务,该服务随后负责将事件分派到相应的界面视图集。目标视图由 Scenic 中的当前视图焦点确定。(请参阅下文中的焦点部分。)
  • 发送 OutputReports 以控制输入设备状态(例如设置 Caps Lock LED)。

在短期内,我们定义了一个 Rust 特征 InputHandler,每个输入处理程序都必须实现该特征。从长远来看,这可能会被 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 以调度到视图。
  • 媒体按钮处理程序:将媒体按钮路由到设置服务(或其他位置)。
  • 键盘布局处理程序:使用当前有效的键盘布局注释键盘事件。
  • 触控板手势处理程序:触控板手势并转发等效的鼠标事件。
  • 输入平滑处理程序:通过对事件求平均值来减少抖动。
  • 焦点处理程序:使用当前聚焦视图的 ViewRef 注释键盘事件。
  • 睡眠模式处理程序:在设备处于睡眠状态时抑制输入。
  • 多点触控华夫饼烙铁魔术按钮处理程序:将触摸手势事件解释为媒体按钮。

在许多情况下,输入处理程序会将事件分派给 Fuchsia 系统服务,例如 Scenic、Accessibility Manager、IME Manager 等。在这种情况下,该事件将被标记为“已处理”,并以这种状态在流水线的其余部分中传播。(请参阅下文中的事件流一致性。)

输入流水线自定义

通过指定要包含的处理程序及其显示顺序来实例化输入流水线。这样一来,添加处理程序、重新排序处理程序或实例化修改后的流水线就非常简单。采用 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 的内核对象与每个视图相关联。

视图按层次结构进行组织,子视图只能影响其父视图边界内的屏幕空间,即采用严格的包含模型。”当某个视图是图表中另一个视图的父视图时,父视图会保留对其子视图的某些控制权,尤其是在内容放置和事件路由方面。

查看树状视图

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

root -> accessibility view -> sysUI -> {some view, some other view, yet
another view}

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

路由图形事件

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

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

专注

为了确定当前正在运行的哪个软件应接收给定的输入事件,我们需要了解视图焦点。这是当前“有效”且准备好接收事件的视图。不过,在实践中,多个视图可能对每个事件感兴趣。

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

有些处理程序会关注单个聚焦视图,而有些处理程序会关注整个聚焦链。例如:

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

焦点事件作为一种特殊的 InputEvent 通过输入流水线移动,并且与其他输入事件严格有序。

事件流一致性

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

  • 键盘:“a”键按下 ->“a”键松开
  • 鼠标:悬停 -> 悬停 -> BUTTON_DOWN -> BUTTON_UP
  • 触控:手指按下 -> 移动 -> 移动 -> 移动 -> 手指抬起

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

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

性能

可接受的延迟时间

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

由于此 RFC 中描述的输入系统在比运行时和基于运行时构建的界面更低的级别运行,因此用户会感受到系统输入延迟,以及处理和渲染对输入事件的响应所花费的时间造成的任何延迟。因此,输入系统应尽可能快,以便为应用和运行时留下尽可能多的“延迟时间预算”。

此外,时间安排的一致性也很重要。即使平均事件传送时间较短,事件时间的高度可变性也会影响用户体验,因此除了平均值之外,还应查看延迟时间分布。

提高性能

减少输入架构延迟时间的最佳方法是尽量减少不必要的进程上下文切换。每次进入和退出内核(通常需要运行调度程序)都会使事件的时间安排产生变化。

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

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

国际化和输入上下文

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

系统应允许不同的视图具有不同的有效输入法。例如,用户可能在用一种语言聊天时,用另一种语言撰写电子邮件。产品可以选择强制执行单一的系统级语言区域设置或有效输入法,但架构必须支持每个视图的独立输入上下文。如何确切地存储这些设置可能会在未来决定。

除了将事件路由到正确的视图之外,输入流水线(以及关联的系统组件,如 IME 管理器)在解读输入事件时,还会使用相应视图的输入上下文。例如,输入流水线会使用有关输入上下文(事件发生于该上下文中)中处于活动状态的键盘布局的信息来注释实体键盘事件。与焦点变化一样,有效键盘布局的变化应被视为输入事件,并与其他事件按顺序处理,以避免与状态变化发生竞态条件。

无障碍

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

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

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

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

安全注意事项

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

此外,还应考虑界面欺骗攻击,例如点击劫持。错误导向的输入事件可用于在未经用户同意的情况下授予权限(例如,将点击操作路由到恶意网站上的按钮)。虽然很难在平台级别完全防止这种情况,但输入架构必须确保图形事件仅传递到正确的界面组件,并且产品所有者可以轻松了解输入事件在系统中的流向。

隐私注意事项

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

测试

测试输入流水线

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

封闭式测试存在许多挑战,这些挑战不在本 RFC 的讨论范围内。

测试所有其他内容

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

输入行业

支持每种不同类型的输入设备都会带来显著的复杂性,超出此处描述的高级架构。后续 RFC 将解决这些细节问题。主要输入垂直行业包括:

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

文档

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

考虑的替代方案

本部分仅列出了所讨论的(众多)替代方案中的一部分。

将根 presenter 演变为流水线

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

特定于产品的 Scenic

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

虽然这种方法在任何单个产品中都能很好地发挥作用,但它需要将单个产品的任何输入自定义项都纳入图形引擎中。这可能意味着,每种新产品类型都需要专门的 Scenic 实现。这种方法在未来可能是一种有价值的优化,但对于当前的使用情形来说过于繁重。