プロトコルリファレンス
このページは、パイプの上に現れるそのままの VXP です。どの言語でクライアントを書く 人にも向けています。Rust の SDK の型は、ちょうどこのバイト列にシリアライズされま す。その背景にある考え方は、先に拡張機能をつくるを読んでください。
現在のバージョンは VXP 1.7 です。
トランスポート
Section titled “トランスポート”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。
secrets
Section titled “secrets”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。
キーボードフォーカスの契約
Section titled “キーボードフォーカスの契約”パネルが 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; としてください。領域を持たないパネルもそ
のまま動き、パネル全体が欄として扱われます。
テーマモジュール
Section titled “テーマモジュール”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はそのもと ですでにゼロになります。
assets
Section titled “assets”パネルに表示する画像です。取得とデコードは拡張機能の側で行い、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。
commands
Section titled “commands”利用者が割り当てられる、名前の付いたコマンドです。ケーパビリティは不要です。
op |
フィールド | 応答 |
|---|---|---|
register |
name |
ok。登録済みの名前でも同じ |
unregister |
name |
ok。登録されていない名前でも同じ |
登録したコマンドは、利用者が拡張機能用のアクション枠に割り当て、その枠を SteamVR
でバインドするまで何もしません。そのあとは押されるたびに command_invoked イベント
が届きます。登録は接続とともに消えるので、hello のたびに登録し直してください。名前
の規則はパネル名と同じです。
エラー: invalid_name、internal。
settings
Section titled “settings”マニフェストの [[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 = 10min = 1max = 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。
strings
Section titled “strings”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 以上) |
バージョン履歴
Section titled “バージョン履歴”| 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 |
マニフェストにも、古いホストが拒否するキーが増えることがあります。どれがそうかは クイックスタートにあります。