mcpField
H
handle-input

handle-input

github.com/even-realities開発79135 回インストール1 週間前に更新
リポジトリ

handle-input について

Even Hub G2アプリ開発者向けに、入力、ジェスチャー、ライフサイクルイベントの扱い方を解説します。コンテナごとのイベント振り分け、Protobufのゼロ値省略、終了時のクリーンアップを扱います。

主な機能

  1. 01タッチジェスチャーのイベント振り分け
  2. 02システムイベントとライフサイクルイベント
  3. 03Protobufのゼロ値省略の解説
  4. 04イベント処理と終了パターン

活用シーン

  1. 01テキストコンテナのクリック問題の調査
  2. 02リスト選択とスクロールの処理
  3. 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.textEvent1 (SCROLL_TOP_EVENT)
下にスワイプevent.textEvent2 (SCROLL_BOTTOM_EVENT)
1 回押すevent.sysEventundefined / 0
2 回押すevent.sysEvent3 (DOUBLE_CLICK_EVENT)

重要な点:テキストコンテナーでのクリックとダブルクリックは、textEvent ではなく sysEvent として発生します。textEvent として発生するのはスクロールジェスチャーだけです。

リストコンテナーがアクティブな場合
ジェスチャーイベントフィールド詳細
上/下にスワイプ(内部処理)SDK がリストを内部でスクロールするため、イベントは発生しません
1 回押すevent.listEvent.currentSelectItemIndex = 選択された項目のインデックス
2 回押すevent.sysEvent.eventType = 3
システムイベント(常に使用可能)
ジェスチャーイベントフィールドeventType
フォアグラウンドに入るevent.sysEvent4 (FOREGROUND_ENTER_EVENT)
フォアグラウンドから離れるevent.sysEvent5 (FOREGROUND_EXIT_EVENT)
異常終了event.sysEvent6 (ABNORMAL_EXIT_EVENT)
システム終了event.sysEvent7 (SYSTEM_EXIT_EVENT)

OsEventTypeList enum の値

値名前説明
0CLICK_EVENT1 回押す(G2 または R1)
1SCROLL_TOP_EVENT上にスワイプ
2SCROLL_BOTTOM_EVENT下にスワイプ
3DOUBLE_CLICK_EVENT2 回押す(G2 または R1)
4FOREGROUND_ENTER_EVENTアプリがフォアグラウンドに移動
5FOREGROUND_EXIT_EVENTアプリがバックグラウンドに移動
6ABNORMAL_EXIT_EVENT予期しない切断
7SYSTEM_EXIT_EVENTシステムレベルの終了(例:ユーザーが終了確認ダイアログで終了を確定)
8IMU_DATA_REPORTIMU データサンプル

イベントモデル

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

コメント