input
input 把用户对本应用绘图面的直接操作变成有界、类型化事件。它不暴露 DOM Event、浏览器全局监听器、原始设备对象或跨应用输入。
基本示例
下面在按下指针时记录逻辑坐标:
await host.input.focus('main');
host.events.on('input.pointer', (event) => {
if (event.canvasId !== 'main' || event.kind !== 'down') return;
beginStroke(event.pointerId, event.x, event.y, event.pressure);
});
适用场景
- 游戏、画板、图表和自绘控件的指针交互。
- 键盘快捷键和非文本按键。
- 鼠标、触控板或等价设备产生的滚轮/平移增量。
- 在多个应用绘图面之间请求焦点。
文本输入、输入法合成、候选词和字符边界查询使用 ime,不能只靠 input.key 重建。
L1 边界
宿主独占的是应用视图的系统焦点以及系统分派给该视图的真实输入事件。guest 无法自行观察这些事件;手势识别、快捷键映射、命中测试和控件状态都能由事件流在 guest 中实现,不进入 L1。
能力声明
{
"capabilities": ["host:input.focus"],
"events": ["input.pointer", "input.key", "input.wheel", "input.focusChange"]
}
指针、键盘和滚轮事件只有在应用拥有对应绘图面且获得焦点时才送达。宿主能力表仍应明确公布事件名和 schema。
Reference
方法
- input.focus():请求把输入焦点交给本应用绘图面。
事件
- input.pointer:指针进入、移动、按下、抬起、取消和离开。
- input.key:按键按下或抬起;用于控制键和快捷键。
- input.wheel:二维滚动增量。
- input.focusChange:绘图面获得或失去输入焦点。
事件数据
PointerEvent {
canvasId,
kind: "enter" | "move" | "down" | "up" | "cancel" | "leave",
pointerId: u32,
pointerType: "mouse" | "touch" | "pen" | "unknown",
x: f32, y: f32,
buttons: u16,
pressure?: f32,
coalesced: Array<{ x: f32, y: f32, pressure?: f32 }>,
droppedMoves: u32,
modifiers: ModifierSet
}
KeyEvent {
canvasId,
kind: "down" | "up",
key: KeyValue,
repeat: boolean,
modifiers: ModifierSet
}
WheelEvent {
canvasId,
x: f32, y: f32,
deltaX: f32, deltaY: f32,
modifiers: ModifierSet
}
FocusChangeEvent {
canvasId,
focused: boolean,
reason: "requested" | "user" | "system" | "hidden" | "destroyed" | "trusted-ui"
}
ModifierSet { shift: boolean, control: boolean, alt: boolean, meta: boolean }
KeyValue = single-unicode-scalar
| "Backspace" | "Tab" | "Enter" | "Escape" | "Delete" | "Insert"
| "ArrowLeft" | "ArrowRight" | "ArrowUp" | "ArrowDown"
| "Home" | "End" | "PageUp" | "PageDown"
| "F1" | "F2" | "F3" | "F4" | "F5" | "F6"
| "F7" | "F8" | "F9" | "F10" | "F11" | "F12" | "Unidentified"
坐标使用绘图面的逻辑像素,不是屏幕坐标。pointerId 只在当前指针活动期内有意义,不能作为设备标识。
buttons 是位掩码:bit 0 主按钮、bit 1 次按钮、bit 2 中键、bit 3 后退、bit 4 前进,其余位必须为 0。pressure 在设备能提供时为 [0, 1],否则省略。文本提交使用 ime;input.key 的单 Unicode 标量只用于快捷键判断,不能绕过输入法直接插入文本。
coalesced 只在 move 中非空,按时间顺序包含上次已交付事件之后、当前 {x,y,pressure} 之前被合并的样本,不重复当前点;其他 kind 必须是空数组。数组只保留最新 input.maxCoalescedPointerMoves 个中间样本,更早被省略的数量精确写入 droppedMoves;非 move 时该值必须为 0。
buttons 使用事件发生后的按钮状态:down 已包含新按下按钮,up 已移除刚释放按钮,cancel 固定为 0;enter/leave 如实保留当时仍按下的其他按钮。repeat 只可能在 key.kind === "down" 时为 true,up 固定为 false。滚轮事件的 {x,y} 是合并窗口内最后一个样本的位置;合并后的 deltaX/deltaY 分别等于所有被合并样本的代数和,不能只保留方向或峰值。
原生 WASM 示例
原生 guest 从统一事件队列读取类型化事件:
HostEvent event;
while (host_event_next(&event)) {
if (event.type == INPUT_POINTER && event.pointer.kind == POINTER_DOWN) {
begin_stroke(event.pointer.pointer_id, event.pointer.x, event.pointer.y);
}
}
事件队列满时宿主可以按上述规则合并连续 move 和 wheel,但不能静默丢弃 down、up、cancel 或 focusChange;无法容纳不可合并事件时按版本级 event-overflow 规则终止会话。
生命周期与焦点
- 事件仅在对应应用视图存活时送达。
- 应用启动时所有绘图面均视为未聚焦;焦点布尔值每次变化恰好发送一个
focusChange。 - 获得焦点的
focusChange必须排在该焦点下第一个 pointer/key/wheel 之前;失焦时先为仍按下的指针发送cancel并终止按键状态,再发送focusChange(focused:false),之后不得送达输入事件。 focus()请求已经聚焦的绘图面时直接成功且不产生重复事件;从本应用另一绘图面切换时,旧面的取消与失焦事件先入队,新面的获得焦点事件随后入队,方法才可成功返回。两面的 reason 都是requested。focus只允许本应用可见且没有可信宿主 UI 覆盖时调用;不额外要求用户手势,但不能从后台窃取系统焦点。- 页面隐藏、系统手势或可信 UI 覆盖时暂停输入。
安全与预算
- 信任档位:green;provenance 为
app-context。 - 不返回屏幕绝对坐标、设备序列号、全局按键状态或其他应用输入。
- 字符串、坐标、压力和修饰键必须经过 schema 限制。
- v0.1 预算键为
input.maxEventsPerSecond(每个应用交付的非合并事件数)和input.maxCoalescedPointerMoves(一次 pointer 事件最多代表的移动样本数)。 - 超过事件速率时只能继续合并
move/wheel;若非合并事件本身超过预算,按版本级event-overflow终止会话,不能改写用户输入历史。 - 宿主不得把密码管理器、系统快捷键或保留组合键泄露给 guest。
不属于本 API
手柄、传感器、全局热键、无障碍语义树和拖放文件都不是 v0.1 input。它们有不同资源、权限或生命周期,需要独立准入。
一致性测试
至少覆盖多指针、初始未聚焦、失焦取消与 focusChange 顺序、坐标缩放、事件合并、按键 repeat、保留快捷键、绘图面销毁、后台暂停,以及 JS/原生 guest 事件次序一致性。