handle-input について
Even Hub G2アプリ開発者向けに、入力、ジェスチャー、ライフサイクルイベントの扱い方を解説します。コンテナごとのイベント振り分け、Protobufのゼロ値省略、終了時のクリーンアップを扱います。
主な機能
- 01タッチジェスチャーのイベント振り分け
- 02システムイベントとライフサイクルイベント
- 03Protobufのゼロ値省略の解説
- 04イベント処理と終了パターン
活用シーン
- 01テキストコンテナのクリック問題の調査
- 02リスト選択とスクロールの処理
- 03アプリ終了とリソースのクリーンアップ
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ even-realities/ everything-evenhub --skill handle-inputインストール後、Claude Code で/handle-inputを入力して読み込まれたか確認します。
詳細
入力の処理
Even Hub G2 アプリでのユーザー入力、ジェスチャー、ライフサイクルイベントの処理ガイドです。
入力ソース
| ソース | ジェスチャー | 備考 |
|---|---|---|
| G2 タッチパッド(テンプル) | 押す、2 回押す、上にスワイプ、下にスワイプ | 主な入力 |
| R1 タッチパッド(リング) | 押す、2 回押す、上にスワイプ、下にスワイプ | オプションのアクセサリー。同じジェスチャーセットを使用 |
| IMU(加速度計/ジャイロスコープ) | 頭部の向き、モーションデータ | device-features スキルを参照 |
イベントのルーティング規則
イベントのルーティング方法は、アクティブなコンテナーの種類(isEventCapture: 1 を持つコンテナー)によって異なります。ページごとにイベントをキャプチャできるコンテナーは 1 つだけです。
テキストコンテナーがアクティブな場合
| ジェスチャー | イベントフィールド | eventType |
|---|---|---|
| 上にスワイプ | event.textEvent | 1 (SCROLL_TOP_EVENT) |
| 下にスワイプ | event.textEvent | 2 (SCROLL_BOTTOM_EVENT) |
| 1 回押す | event.sysEvent | undefined / 0 |
| 2 回押す | event.sysEvent | 3 (DOUBLE_CLICK_EVENT) |
重要な点:テキストコンテナーでのクリックとダブルクリックは、textEvent ではなく sysEvent として発生します。textEvent として発生するのはスクロールジェスチャーだけです。
リストコンテナーがアクティブな場合
| ジェスチャー | イベントフィールド | 詳細 |
|---|---|---|
| 上/下にスワイプ | (内部処理) | SDK がリストを内部でスクロールするため、イベントは発生しません |
| 1 回押す | event.listEvent | .currentSelectItemIndex = 選択された項目のインデックス |
| 2 回押す | event.sysEvent | .eventType = 3 |
システムイベント(常に使用可能)
| ジェスチャー | イベントフィールド | eventType |
|---|---|---|
| フォアグラウンドに入る | event.sysEvent | 4 (FOREGROUND_ENTER_EVENT) |
| フォアグラウンドから離れる | event.sysEvent | 5 (FOREGROUND_EXIT_EVENT) |
| 異常終了 | event.sysEvent | 6 (ABNORMAL_EXIT_EVENT) |
| システム終了 | event.sysEvent | 7 (SYSTEM_EXIT_EVENT) |
OsEventTypeList enum の値
| 値 | 名前 | 説明 |
|---|---|---|
| 0 | CLICK_EVENT | 1 回押す(G2 または R1) |
| 1 | SCROLL_TOP_EVENT | 上にスワイプ |
| 2 | SCROLL_BOTTOM_EVENT | 下にスワイプ |
| 3 | DOUBLE_CLICK_EVENT | 2 回押す(G2 または R1) |
| 4 | FOREGROUND_ENTER_EVENT | アプリがフォアグラウンドに移動 |
| 5 | FOREGROUND_EXIT_EVENT | アプリがバックグラウンドに移動 |
| 6 | ABNORMAL_EXIT_EVENT | 予期しない切断 |
| 7 | SYSTEM_EXIT_EVENT | システムレベルの終了(例:ユーザーが終了確認ダイアログで終了を確定) |
| 8 | IMU_DATA_REPORT | IMU データサンプル |
イベントモデル
interface Text_ItemEvent {
containerID?: number
containerName?: string
eventType?: number // 1 = scroll up, 2 = scroll down
}
interface List_ItemEvent {
containerID?: number
containerName?: string
currentSelectItemName?: string
currentSelectItemIndex?: number // 0-based; undefined when 0 (protobuf)
eventType?: number
}
interface Sys_ItemEvent {
eventType?: number // undefined/0 = single click, 3 = double click
eventSource?: EventSourceType // 0=null, 1=glasses right, 2=ring, 3=glasses left
imuData?: IMU_Report_Data
systemExitReasonCode?: number
}
Protobuf のゼロ値省略
SDK は内部で protobuf を使用します。ゼロ/デフォルト値(0、false、空文字列)のフィールドは、ゼロ値ではなく undefined になります。次の項目に影響します。
sysEvent.eventType— シングルクリックは0ですが、undefinedとして届きますlistEvent.currentSelectItemIndex— 最初の項目は0ですが、undefinedとして届きます- 値が
0のその他の数値フィールド
必ず null 合体演算子を使用してください:event.sysEvent.eventType ?? 0、event.listEvent.currentSelectItemIndex ?? 0。
イベント処理の完全なテンプレート
import { waitForEvenAppBridge } from '@evenrealities/even_hub_sdk'
const bridge = await waitForEvenAppBridge()
const unsubscribe = bridge.onEvenHubEvent(event => {
if (event.listEvent) {
// List item selected (single press on list container)
const idx = event.listEvent.currentSelectItemIndex ?? 0
console.log('Selected index:', idx)
return
}
if (event.textEvent) {
// Scroll on text container (NOT clicks — those come via sysEvent)
const type = event.textEvent.eventType ?? 0
if (type === 1) {
// Swipe up / scroll up
} else if (type === 2) {
// Swipe down / scroll down
}
return
}
if (event.sysEvent) {
const type = event.sysEvent.eventType ?? 0
if (type === 0) {
// Single press (on text container, or system-level)
} else if (type === 3) {
// Double press
} else if (type === 4) {
// App resumed (foreground enter)
} else if (type === 5) {
// App backgrounded (foreground exit)
}
return
}
})
// Always clean up on teardown
// unsubscribe()
G2 と R1 の違い
G2(テンプルタッチパッド)と R1(リングタッチパッド)は、同じジェスチャーセットを使用します。両者を区別するには、Sys_ItemEvent の eventSource を確認してください。EventSourceType の値によって、入力元が左腕、右腕、リングアクセサリーのいずれかを判別できます。
終了方法
すべてのアプリで、メガネ/リングの操作による終了手段を用意してください。独自の確認 UI を作成するのではなく、SDK に組み込まれているシステム終了ダイアログを使用してください。
標準的なパターン — ダブルタップでシステム終了ダイアログを表示:
if (eventType === 3) { // DOUBLE_CLICK_EVENT
// Show the system exit dialog. Don't clean up resources here —
// the user can still cancel. If they confirm, the SDK fires
// SYSTEM_EXIT_EVENT (7) and you clean up in that handler.
bridge.shutDownPageContainer(1)
return
}
shutDownPageContainer のモード:
shutDownPageContainer(0)— 確認なしで即時終了shutDownPageContainer(1)— システム終了確認ダイアログ(推奨)
shutDownPageContainer(1) を呼び出す前に unsubscribe() を呼び出したり、ハードウェアを停止したり、状態をフラッシュしたりしないでください。そうすると、ユーザーがキャンセルをタップした場合、アプリは画面に表示されたままなのにイベントを受け付けなくなります。クリーンアップは ABNORMAL_EXIT_EVENT/SYSTEM_EXIT_EVENT のハンドラーで行ってください。
ライフサイクルイベント
アプリを適切に動作させるには、次の4つのライフサイクルイベントをすべて処理してください。
| イベント | 使用するタイミング |
|---|---|
FOREGROUND_ENTER_EVENT (4) | 現在の状態を再描画し、タイマー/IMUを再開します |
FOREGROUND_EXIT_EVENT (5) | 保留中の状態を setLocalStorage に書き込み、タイマーを一時停止します |
ABNORMAL_EXIT_EVENT (6) | ハードウェアを停止し(imuControl(false)、audioControl(false))、購読を解除して、状態を書き込みます |
SYSTEM_EXIT_EVENT (7) | ABNORMAL_EXITと同じクリーンアップを行います。ユーザーがシステムダイアログで終了を確定した場合に発生します |
重要な注意事項
- テキストコンテナのクリックは
textEventではなくsysEventに送られます。textEventが発生するのはスクロールジェスチャーのみです。イベント処理のバグで最もよくある原因です。 - クリーンアップ:
bridge.onEvenHubEvent()は購読解除関数を返します。コンポーネントの破棄時には必ず呼び出してください。 - ページごとにイベントリスナーは1つのみ:
isEventCapture: 1を指定したコンテナのみが入力イベントを受け取ります。複数のコンテナでisEventCapture: 1を指定すると、SDKは検証エラーを返してページを拒否します。
タスク
$ARGUMENTS