コンテンツにスキップ

プロトコルリファレンス

このページは、パイプの上に現れるそのままの VXP です。どの言語でクライアントを書く 人にも向けています。Rust の SDK の型は、ちょうどこのバイト列にシリアライズされま す。その背景にある考え方は、先に拡張機能をつくるを読んでください。

現在のバージョンは VXP 1.7 です。

vrOS はプロセスを起動する前に拡張機能ごとのエンドポイントを作り、3 つの環境変数を 入れておきます。

変数 中身
VROS_EXT_ENDPOINT 接続先のエンドポイント
VROS_EXT_TOKEN この起動だけのトークン。hello で送り返してください。ログには決して出さないでください
VROS_EXT_ID 拡張機能の id

Windows ではエンドポイントは named pipe の \\.\pipe\vros.ext.<id> です。Linux では $XDG_RUNTIME_DIR/vros/ の中の Unix socket ext-<id>.sock です。名前を組み立て直 すのではなく、変数を読んでください。パイプはプロセスの起動より少し遅れて現れるこ とがあるので、接続は数秒のあいだ再試行してください。

どのフレームも、1 行に 1 つの JSON-RPC 2.0 オブジェクトで、\n で終わります。

  • バッチの配列は拒否されます。
  • 3 MiB を超えるフレームは、通信ではなく破損として扱われます。
  • 行の前後の空白、NUL、バイトオーダーマークは取り除かれ、空行は読み飛ばされます。
  • リクエストの id は符号なし整数です。
  • 未知のフィールドはどこでも無視されます。マイナーがフィールドを足せるのはそのた めです。

リクエストは method に名前空間を書きます。params はその名前空間のリクエスト オブジェクトで、op でタグ付けされます。

{"jsonrpc":"2.0","id":7,"method":"kv","params":{"op":"get","key":"count"}}

応答の result はその名前空間の応答オブジェクトで、result でタグ付けされます。

{"jsonrpc":"2.0","id":7,"result":{"result":"value","value":3,"revision":12}}

ホストからのイベントは通知 — id なし — で、method は "event" です。

{"jsonrpc":"2.0","method":"event","params":{"event":"panel_shown","panel":"overlay"}}

2 種類あり、重なることはありません。

ドメインのエラーは、成功した応答の中に、その名前空間の err として届きます。 code と、たいていは message が付きます。

{"result":"err","code":"value_too_large","message":"…"}

プロトコルのエラーは JSON-RPC の error オブジェクトです。リクエストが名前空間 まで届かなかったことを意味します。

コード 意味
-32700 その行が JSON ではない
-32600 正しいリクエストではない — バッチ、誤った jsonrpc、method のないフレーム
-32601 未知の名前空間
-32602 params がその名前空間のリクエストの形に合わない
-32603 内部エラー
-32001 capability_denied — 名前空間は存在するが、許可されていない
-32002 有効な hello より前にリクエストが届いた。ホストは切断します

応答、イベント、コードは、どれも後のマイナーで増えることがあります。知らない op、result、event、code は、止まる理由ではなく、読み飛ばすものとして扱って ください。

どの接続でも最初のフレームで、起動から 5 秒以内に送ります。

{"jsonrpc":"2.0","id":1,"method":"hello","params":{"id":"com.example.hello","vxp_version":"1.7","token":"…"}}

welcome には、ホストが話すバージョン、利用者が許可したもの — マニフェストの要求 を利用者の同意で絞ったもの、マニフェストと同じ形 — 、利用者のロケール、そして解決 できるときは利用者の vrOS コンテンツフォルダーが入っています。

{"result":"welcome","vxp_version":"1.7","granted":{"bus":["vr.lifecycle"],"overlays":false,"capture":false,"audio":false,"media":false,"osc":false,"network":false,"secrets":false},"locale":"en-US","content_root":"C:\\Users\\you\\Documents\\vrOS"}

face_provide と face_read は、true のときだけ granted に現れます。許可されな かったものは、その接続のあいだずっとそのままです。

拒否のあとには切断が続きます。

{"result":"rejected","code":"version_unsupported","message":"host speaks 1.7 (N and N-1 majors)"}
コード 理由
bad_token トークンがこの起動のものと一致しない
wrong_id id が、このエンドポイントを作った相手と違う
version_unsupported メジャーがホストの 1 つ前より古いか、ホストの現行より新しい
already_connected この拡張機能はすでに接続している

送るマイナーには意味があります。十分に新しいマイナーを伝えたクライアントにしか届 かないイベントがあるからです。自分のコードが実際に理解しているバージョンを送って ください。

自分専用のキー / 値ストアです。最後の書き込みが勝ち、キーごとのリビジョンは増える 一方です。ケーパビリティは不要です。

op フィールド 応答
get key value(value と revision)。存在しないキーは value: null, revision: 0
set key、value(任意の JSON) written(新しい revision と over_quota)
delete key deleted。すでに無いキーでも同じ
list — keys(keys)

キーは 256 バイトまで、値は 64 KiB までです。ストアには拡張機能ごとに 1 MiB のソフ トな上限があります。超えると over_quota が立ち、ペインで拡張機能にバッジが付きま すが、上限を守るために何かが消されることはありません。

マニフェストの [[settings]] で宣言したキーへの set は、その設定の種類と範囲を 満たす必要があります。

エラー: key_too_large、value_too_large、internal。

OS の資格情報ストアの中の、名前の付いたスロットです。secrets が必要です。

op フィールド 応答
set slot、value ok
get slot value(value、空のスロットなら null)
delete slot ok
exists slot exists(exists)

値は 2048 バイトまでで、長いものは切り詰められずに拒否されます。シークレットが KV、設定、ログ、vrOS が表示するものに現れることはありません。

エラー: not_persistable(Linux。まだ資格情報のバックエンドがないので、セッション ごとにたずねてください)、too_large、internal。

op フィールド 応答
publish topic、payload(任意の JSON) ok
subscribe pattern ok
unsubscribe pattern ok

自身のプレフィックス ext.<id>. へは、いつでも送信も購読もできます。それ以外 — vr.lifecycle や他の拡張機能のトピックを含む — には、マニフェストの bus の一覧に 一致するパターンが必要です。パターンはトピックそのものか、.* で終わるプレフィッ クスです。ext.cat.kittyn.twitch.* は ext.cat.kittyn.twitch.chat.message を含み ます。

メッセージは bus イベントとして届きます。vr.lifecycle を購読すると、代わりに vr_lifecycle イベントが届きます。

エラー: not_allowed、internal。

マニフェストのパネルに define は不要です。拡張機能の起動時にホストが読み込んでコ ンパイルし、その名前はロールのキー、overlay と dash-tab です。

op フィールド 応答
props panel、props applied
define panel、source、任意で width、height、anchor defined(revision)
remove panel removed

props は [名前, 値] の組のリストで、最後の値が勝つ形でマージされます。ホストは パネルごとに毎秒およそ 30 回にまとめます。値は型でタグ付けされます。

t v Slint の型
bool true bool
i64 21 int(2⁵³ まで正確)
f64 0.5 float
str "text" string
str_list ["a","b"] [string]
rows [[{"t":"str","v":"row"},{"t":"i64","v":-7}]] 行のモデル
assets ["e:icon:1"] [image]。assets でアップロードしたキーから
{"op":"props","panel":"overlay","props":[["count",{"t":"i64","v":21}],["title",{"t":"str","v":"Hello"}]]}

define は、.slint のソース全体から実行時のオーバーレイパネルを作るか置き換えま す。overlays が必要です。ソースは応答の前にコンパイルされるので、コンパイルエ ラーはコンパイラーのメッセージ付きの compile_failed として返ります。width と height は論理ピクセルで、ホストが既定値を補い、制限します。anchor(world か hmd)はパネルが最初に現れる場所のヒントで、利用者が動かしたら、その配置が優先さ れます。定義し直すとリビジョンが上がります。実行時のパネルはオーバーレイだけで、 remove でマニフェストのパネルは消せません。

パネル名は [a-z0-9-] で 64 バイトまで。ソースは 256 KiB までです。

エラー: invalid_name、source_too_large、compile_failed、no_such_panel、 not_allowed、internal。

パネルが VR キーボードとハードウェアのキーを受け取るのは、ルートのコンポーネントが 次の両方を宣言したときだけです。

  • callback field-focus(bool) — 各テキスト欄の has-focus のエッジごとに呼んでく ださい。ホストが受け取り、拡張機能には届きません。
  • in property <int> field-blur — フォーカスが他へ移るとホストがこれを進めます。 changed field-blur のハンドラーで、欄のフォーカスを外してください。

複数の欄が 1 つのコールバックを共有できます。ホストはフレームごとにエッジを相殺す るので、自分の 2 つの欄のあいだの移動はフォーカスが続いているものとして読まれま す。フォーカス中の欄を消したりページを切り替えたりするときは、フォーカスが失われ たことを自分で伝えてください。破棄された要素がハンドラーを走らせるとは限りません。

クリックで外したときの判定を正確にするには、@vros/theme.slint の ExtFocusRegion を各テキストボックスの中に置き、width: parent.width; height: parent.height; focused: input.has-focus; としてください。領域を持たないパネルもそ のまま動き、パネル全体が欄として扱われます。

import { ExtTheme, ExtFocusRegion } from "@vros/theme.slint"; が、パネルに許さ れた唯一の import です。ホストが利用者の現在の設定で中身を埋めます。

  • ExtTheme.c は現在のパレットです: bg0、bg1、bg2、line、tx-hi、 tx-mid、tx-low、accent、accent-text、ok、warn、err、focus、 live、live-text。
  • ExtTheme.current はテーマの id で、signal、graphite、aura、meow、 ember、cobalt、mono、volt、crimson、daylight、hc-dark、hc-light のいずれかです。OLED モードは別のフラグ ExtTheme.oled で、current は変わりま せん。id を調べるより、パレットを使ってください。
  • 大きさは密度に従います: space-1 から space-6、control-h、hit-min、 radius-card、radius-control、border-default、文字の大きさ type-title、 type-body、type-data、type-label、type-numeral。デスクトップアプリでは is-desktop が true です。
  • reduced-motion は利用者の設定で、motion-press と motion-expand はそのもと ですでにゼロになります。

パネルに表示する画像です。取得とデコードは拡張機能の側で行い、vrOS が代わりに URL を読み込むことはありません。ケーパビリティは不要です。

op フィールド 応答
put key、width、height、frames、loop ok
remove key ok
clear — ok

各フレームは {"rgba_base64": "…", "delay_ms": 100} で、ストレートの RGBA8、デコー ドするとちょうど 幅 × 高さ × 4 バイトです。loop は合計の再生回数で、0 は無限に 繰り返します。

{"op":"put","key":"e:icon:1","width":1,"height":1,"frames":[{"rgba_base64":"/////w==","delay_ms":100}],"loop":0}
上限 値
大きさ 128 × 128 まで
フレーム 60 まで、それぞれ 20〜1000 ms
1 つのアセット デコード後 2 MiB
自分のテーブル 1024 個または 32 MiB。場所を空けるため、もっとも長く使われていないものから追い出されます
同時のアニメーション パネルごとに 24

キーは英字、数字、- _ . : / からなる 1〜128 バイトです。自分のテーブルの中の画像 を指すだけで、ほかの場所には通じません。テーブルは接続が続くあいだ保たれます。アニ メーションはパネルが見えているあいだ描画側の時計で進むので、props で動かさない でください。

エラー: too_large、too_many_frames、budget_full(ホストが混んでいます — 再試 行してください)、bad_key、bad_pixels。

利用者が割り当てられる、名前の付いたコマンドです。ケーパビリティは不要です。

op フィールド 応答
register name ok。登録済みの名前でも同じ
unregister name ok。登録されていない名前でも同じ

登録したコマンドは、利用者が拡張機能用のアクション枠に割り当て、その枠を SteamVR でバインドするまで何もしません。そのあとは押されるたびに command_invoked イベント が届きます。登録は接続とともに消えるので、hello のたびに登録し直してください。名前 の規則はパネル名と同じです。

エラー: invalid_name、internal。

マニフェストの [[settings]] がフォームを宣言します。vrOS は拡張機能が停止してい るあいだも、それを拡張機能のペインに描きます。値は宣言したキーの下の、ふつうの KV の行です。

[[settings]]
key = "send_target"
kind = "text"
label = "Send target"
default = "127.0.0.1:9000"
placeholder = "host:port"
[[settings]]
key = "rate"
kind = "number"
label = "Updates per second"
default = 10
min = 1
max = 60
[[settings]]
key = "mode"
kind = "select"
label = "Mode"
default = "auto"
choices = [
{ value = "auto", label = "Automatic" },
{ value = "manual", label = "Manual" },
]
kind default 規則
text 1 KiB までの文字列 placeholder を付けられます
toggle true か false —
number 範囲内の整数 min と max が必須、min < max、どちらも ±1,000,000 以内
select 選択肢の値のひとつ choices は 2〜8 個、value はそれぞれ一意

設定は 16 個まで。キーは [a-z0-9_-] で 64 バイトまで、ラベルは 200 バイトまでで す。これに反するマニフェストは読み込まれず、ペインに理由が出ます。

op フィールド 応答
get key value(key、value、is_default)
list — values(entries、マニフェストの順)

値は保存されているもの、何もなければ既定値です。通信上に設定の書き込みはありませ ん。利用者が欄を編集すると settings_changed イベントが届き、拡張機能自身の書き込 みは同じキーへのふつうの kv の set です。

エラー: no_such_key、no_schema、internal。

vrOS に組み込まれたカタログから、ロケール解決したテキストを返します。

op フィールド 応答
get prefix、任意で locale strings(実際に返した locale と entries)

返されるのは ext. の下のキーだけで、現在それは vrOS 自身のバンドル型拡張機能が使 うキーを意味します。自分のキーはこのカタログにありません。 自分のプレフィック スには空の結果が返ると考え、翻訳は自分で同梱してください。未知のロケールには en-US を返し、そのことを応答に書きます。locale_changed のたびに取り直してくだ さい。

エラー: prefix_not_allowed、internal。

audio が必要です。vrOS はサウンドを一度デコードしてメモリに保ち、名前の付いたチャ ンネルで再生します。

チャンネル 再生先
soundboard サウンドボードの出力
mic 利用者のマイクへ送り込むデバイス(仮想ケーブルなど)。何かを録音することはありません
ui 操作音。システムの既定の出力に従います
op フィールド 応答
load path、normalize 新しい sound の id を持つ loading、そのあと audio_loaded イベント
unload sound ok
pre_arm channel ok — 最初の再生の前にチャンネルの出力を開いておきます
play sound、targets、looping ok
stop sound ok
stop_all — ok
get_status — status(playing)
list_channels — channels
list_devices — devices(endpoints と default_endpoint)

path は絶対パスで、256 MiB まで、デコードは 30 秒以内に終わる必要があります。 welcome の content_root が利用者の vrOS フォルダーです。load はすぐに応答し、デ コードは裏で終わります。再生は、そのサウンドの audio_loaded イベントが loaded を告げてからにしてください — 再生の経路がデコードすることはありません。

{"op":"play","sound":"s-00000001","targets":[{"channel":"soundboard","gain_pct":80},{"channel":"mic","gain_pct":40}],"looping":false}

ターゲットごとに 0〜100 のゲインを持ちます。再生できないターゲットは飛ばされ、ひと つも再生できなかったときだけ play が失敗します。状態の行には、実際に受け付けたター ゲットが並びます。同じサウンドをもう一度再生すると、前の再生を置き換えます。

エラー: not_found、undecodable、too_long、cache_full、unknown_sound、 unknown_channel、sample_not_cached、no_valid_targets、no_endpoint、 internal。

media が必要です。映像や音声のストリームを vrOS のプレーヤーで再生します。現在は Windows のみで、それ以外ではすべてのリクエストが unsupported を返します。

op フィールド 応答
open key、url、title、そして下のオプション opened(session と source_generation)
set session と、native_controls 以外のオプションのいずれか applied(source_generation)
reload session、url、任意で title applied(新しい source_generation)
close session closed
status session status

オプションと既定値: max_fps(30)、volume_pct(100)、muted(false)、 paused(false)、show_video(true)、native_controls(false)、 controls_on_player(false)。

  • key は枠に付ける自分の名前で、[a-z0-9-]、64 バイトまで。キーごとに開けるセッ ションは 1 つです。
  • url は 8 KiB まで、title は 256 バイトまで。URL は資格情報として扱い、決して ログに出したり保存したりしないでください。
  • max_fps は 1〜120、volume_pct は 0〜100。
  • vrOS が同時に動かすセッションは、すべての拡張機能を合わせて 4 つです。5 つ目には too_many が返ります。

映像を隠しても値を変えても、ストリームとその音声は動き続けます。ソースを変えるのは reload だけで、そのとき source_generation が上がります。

状態は media_snapshot イベントで届きます。完全で、まとめられており、1.4 以上で hello を送ったクライアントにだけ届きます。各セッションは playback(opening、 buffering、playing、paused、ended、またはコード付きの failed)と、それと は別に video(disabled、warming、ready、recovering)を報告するので、音声 は健全なまま映像だけが回復中、ということがありえます。

native_controls: true にすると、プレーヤーが自前の一時停止、音量、停止の操作を描 き、完了した操作がそれぞれ絶対値の動作 — set_paused、set_volume、stop — を持 つ media_control イベントとして届きます。自分の set や close で適用してくださ い。その前に、source_generation がセッションの現在のものと一致するか確かめてくだ さい。controls_on_player は、その操作を映像の下ではなく映像の上に置きます。

エラー: invalid_url、invalid_key、invalid_value、unsupported、 too_many、no_such_session、busy、endpoint_unavailable、open_failed、 playback_failed、dimension_limit。

デバイスに依存しないフェイストラッキングです。提供側(face_provide)がソース を差し出し、利用者がフェイストラッキングのページで目と口のソースを選び、読み取り 側(face_read)が選ばれたものを読みます。どちらの許可も、利用者がその拡張機能 のためにオンにするまでオフで、動いている最中に取り消されることもあります。フェイス の更新は、1.5 以上で hello を送ったクライアントにだけ届きます。

op フィールド 応答
register source registered(generation)
unregister source_id ok
publish source_id、generation、sample ok
subscribe eyes、mouth ok
unsubscribe — ok

ソースは自分のことを正直に説明します。

{"op":"register","source":{"id":"sim","name":"Simulated eyes + mouth","device":"Demo","transport":"VXP","capabilities":{"combined_gaze":true,"eye_lids":true,"expressions":["jaw_open"]},"max_rate_hz":30}}

id は [a-z0-9-] で 64 バイトまで、3 つのテキストの欄は 128 バイトまで、 max_rate_hz は 1〜240 です。capabilities では combined_gaze、per_eye_gaze、 eye_lids、そして 37 個の固定の集合(jaw_open、mouth_smile_left、 tongue_out など)から選んだ口の expressions を名乗れます。1 つの拡張機能が登録 できるソースは 4 つまで、vrOS 全体では 16 までです。

求められたときだけ送ってください。 どの face_update イベントにも demands が あり、自分のソースのどれが、目、口、またはその両方で求められているかを示します。そ れ以外のときは何も送らず — ホストは not_demanded を返します — 求めがなくなった瞬 間に止めてください。

サンプルは増えていく sequence、取得してからの経過時間 age_ms(250 未満。それ以 上は古すぎます)、そして eyes、mouth、またはその両方を持ちます。

  • 視線の向きはヘッドセット空間の単位ベクトルです: +X が右、+Y が上、−Z が前。
  • まぶたの closed_left / closed_right は、0 が開き、1 が閉じ。
  • convergence_m はメートル、confidence は 0〜1。
  • 口の weights は {"expression": "jaw_open", "value": 0.5} の形で、それぞれ 0〜1。

測ったものだけを送ってください。欠けたチャンネルは不明であってゼロではなく、視線が ないことは目を閉じていることを意味しません。

読み取り側は subscribe を呼び、face_update イベントを読みます。それぞれが差分で はなく完全なスナップショットで、許可、選ばれた eyes と mouth とその経過時間、そ して求めが入っています。途切れずに読み出し、サンプルを信じる前に、自分の側の配送の 遅れを age_ms に足してください。

エラー: not_granted、invalid_source、invalid_sample、too_many、 no_such_source、stale_generation、out_of_order、not_demanded、 unsupported。

event フィールド いつ
shutting_down — vrOS が拡張機能を止めようとしている。書き出して 0 で終了してください
vr_lifecycle state: detached、attached、quitting VR ランタイムが現れた、または去った(vr.lifecycle を購読している場合)
bus topic、payload 購読したトピックにメッセージが来た
panel_shown panel パネルがどこか — オーバーレイ、ダッシュのタブ、デスクトップ — で見えるようになった
panel_hidden panel …そしてどこでも見えなくなった。必ず panel_shown と対になります
panel_event panel、callback、args パネルのコールバックが呼ばれた。args は props と同じ型付きの値
command_invoked command 利用者がコマンドのひとつを実行した
settings_changed key、value 利用者が設定を編集した。値はすでに検証済み
locale_changed locale UI の言語が変わった。strings を取り直し、言葉を送り直してください
audio_loaded sound、outcome load が終わった。長さと音量を持つ loaded、またはコード付きの failed
media_snapshot granted、sessions メディアの状態が変わった(1.4 以上)
media_control key、session、source_generation、action ネイティブ操作のあるセッションでプレーヤーが操作された(1.6 以上)
face_update state フェイスの許可、選択、求めが変わった、または新しいサンプルが届いた(1.5 以上)
VXP 加わったもの
1.0 ハンドシェイク、kv、secrets、bus、panel、commands、ライフサイクル・バス・パネル・コマンドのイベント
1.1 settings と strings、マニフェストの [[settings]]、settings_changed と locale_changed のイベント
1.2 audio、audio_loaded イベント、welcome の content_root
1.3 assets と assets のプロパティ型、define の anchor ヒント、number と select の設定
1.4 media と media_snapshot イベント
1.5 face、face_provide と face_read のケーパビリティ、face_update イベント
1.6 プレーヤーのネイティブ操作: native_controls、media_control イベント、reload の title
1.7 open と set の controls_on_player

マニフェストにも、古いホストが拒否するキーが増えることがあります。どれがそうかは クイックスタートにあります。