Date: 2026-05-30 · Author: Claude research session for Mike Wise
Question: How should senckenberg.messelpit.viewer_xr reconcile its
in-VR floating panel (MesselpitMenuTool) with Kit's stock right-B
settings menu (XRMenuTool)? Is there a "right" way to surface
app-specific menu items in Omniverse Kit XR, and what does the broader
VR ecosystem do?
- No public API exists today to add items into Kit's stock XR settings
menu. The right-B menu (
omni.kit.xr.ui.stage/xr_tools/xr_menu_tool.py) builds its panel from hard-coded internal modules. Subclassing or monkey-patching it is the only way in, and both are fragile. - Industry convention is firmly "B/Y opens the app menu, not the system menu". Unreal's VR Template binds B to its quick-menu; Half-Life Alyx uses hold-Y; Meta's own button-mapping tech note labels the dedicated ≡ Menu button as the recommended app-menu entry point. Quest 3 Touch Plus keeps the ≡ button on the left controller; Kit ignores it. The system overlay (Meta dashboard, SteamVR dashboard) has its own reserved button — apps cannot intercept it.
- Recommendation: keep the dual-handed left-Y binding as the default
and stop trying to preserve the stock settings menu in single-right
mode. The stock menu contains exactly two settings the user cares
about (locomotion speed, render quality); reproducing them in
MesselpitMenuToolis much cheaper than fighting Kit's tool framework for a private extension hook. See "Recommendation" below.
Searching the Omniverse Kit docs and developer forums for "XRMenuTool",
"XR settings menu", "extend XR menu", and omni.kit.xr.ui.stage returned
zero results describing a supported way to add items into the stock
right-B menu. The closest references are:
- The forum thread "XR Core events, action maps and eventgenerators
for modifying controls" discusses overriding controller
bindings via
bind_event_generator()but never addresses extending the existing menu. One commenter wants to "override the default menu behavior" — no answer is given. - The forum thread "Customize UI design for VR experience"
is about building a new VR UI; the NVIDIA moderator points the user
at
kit-xr-samplesand the questions about widget layout go unanswered. - The official Input & Interaction docs document the
/user/hand/*action-map structure and say action maps live in profile config — but say nothing about menu extension.
From the prior in-VR-UI research (docs/in-vr-ui-research-2026-05-30.md)
we already know the stock menu lives at
omni.kit.xr.ui.stage/xr_tools/xr_menu_tool.py and is the canonical
XRToolComponentBase reference implementation. Its panel content is
not data-driven — it instantiates fixed widget classes from
omni.kit.xr.advanced_settings_ui / omni.kit.xr.system.openxr.ui. There
is no register_settings_item() API. Adding to it from outside means
either (a) subclassing the tool and re-binding the action, which gives us
the same situation as our own tool but with more coupling to NVIDIA
internals, or (b) editing the vendored Kit source — which the project
CLAUDE.md explicitly warns against.
The action-map files in
source/extensions/senckenberg.messelpit/xrmanifests/action_maps/ already
demonstrate the supported extension model: ship a JSON manifest at higher
priority, and Kit's action-map merger will either add a new tool (the
dual-handed left-Y case — both tools coexist) or override an existing
binding (the single-right right-B case — our tool wins, stock loses).
This is working as designed; there's no third option that quietly inserts
items into a tool you don't own.
The kit-xr-samples repo ships one sample —
omni.kit.xr.samples.usd_scene_ui — which builds a SceneUI menu using
the same XRSceneView + WidgetComponent shape we landed on for our
own panel. It does not demonstrate extending the stock settings
menu. NVIDIA's own answer to "I want a VR menu" is "build a separate
SceneUI panel" — exactly what MesselpitMenuTool is.
Meta's Touch Button Mapping Best Practices (Horizon developer blog) is explicit:
| Button | Recommended use |
|---|---|
| A / X | Primary select |
| B / Y | "Backing out or cancelling a selection" (B preferred over Y) |
| Trigger | Select / fire |
| Grip | Grab |
| ≡ Menu | "Bring up or dismiss a pause or in-app menu" ("strongly recommended … provides a more consistent experience across titles") |
| Oculus / Meta | Reserved system button, not available to developers |
Two important wrinkles for our project:
- The ≡ menu button still exists on Quest 3 Touch Plus, but only on the left controller (it's the three-line icon next to X/Y). Touch Plus docs. We could in principle bind to it.
- Kit's action maps don't expose it. The stock and our custom
oculus_touch.jsonfiles bind to A/B/X/Y/triggers/grips but not to/input/menu/click. Adding it would require a custom binding plus verifying that Meta's Horizon Link OpenXR runtime forwards it to the app (some runtimes reserve it for the system overlay — see 2.4).
- Unreal Engine VR Template binds B to a quick menu (Reset Orientation / Reset / Exit). UE docs.
- Half-Life Alyx uses hold-Y for its pause menu on Rift/Quest — a deliberate long-press to avoid clashes with quick taps. Steam community guidance. This is the closest analogue to option (d) in the question.
- Unity XR Interaction Toolkit documents the menu button as "used
to pause, go back, or otherwise exit gameplay"; on Vive it's remapped
to
primaryButtonfor cross-platform consistency. Unity XR docs. - Walkabout / Job Simulator / Vacation Simulator etc. — convention is overwhelmingly "B or Y opens the app menu, hold for a moment to disambiguate from back/cancel" or "wrist/hand menu, no button".
Meta's design docs have moved toward hand/wrist menus as the recommended pattern for hand-tracking-first apps. The pattern is "no button at all — look at the back of your left wrist to summon the menu". This is worth noting as a future option (especially for the eventual guided-tour mode for Senckenberg visitors who may not have controllers at all), but is out of scope for the current controller-driven build.
OpenXR has both /input/menu/click and /input/system/click. The
system path is universally reserved by the runtime — pressing it
opens the SteamVR dashboard or the Meta dashboard, and the
application's event loop pauses (OnApplicationFocus(false) in
Unity). See Khronos OpenXR interaction class notes
and the Unity discussion "How to detect Universal Menu through
OpenXR?". This means there is no "share the
button with the system" trick — the system and app menus are always
on different buttons.
The dashed /input/menu/click is technically available to apps but in
practice some SteamVR/WMR runtimes also reserve it. The widely-supported
binding for an in-app menu is therefore a face button, which is what
every shipping app uses.
Adopt option (a) with one improvement. Specifically:
-
Dual-handed (Quest with both controllers) — keep left-Y for our menu, leave right-B with the stock Kit settings menu. This is the current default and matches no convention specifically but trades well: every shipping VR app uses B/Y for the app menu, and we put our app menu on Y (one of the two), reserving B for Kit's system-style settings. Document it in-headset (welcome panel / tooltip on first XR session).
-
Single-right Touch — accept that we lose the stock Kit settings menu in this mode, and replicate its two useful settings inside
MesselpitMenuTool. The stock menu's contents are largely navigation/locomotion speed and render-quality presets. Both are already-exposedcarb.settingskeys:xr.navigation.speed(locomotion speed)/rtx/*render presets (or/persistent/rtx/...)
Adding a "Comfort" tab to
MesselpitMenuToolwith a speed slider plus a "Quality: Low/Med/High" segmented control closes the gap completely. This is exactly the pattern the project CLAUDE.md describes under "How to extend the UI". -
Do NOT attempt to subclass / monkey-patch
XRMenuToolto inject our items into the stock panel. The Kit team did not design that extension point. Past attempts at touching upstream Kit internals in this repo (see git history around theapplication_modestartup) have produced fragile results. -
Defer the ≡-menu-button option. Binding to
/input/menu/clickon Touch Plus is technically possible and would be the "most conventional" choice on Quest, but it needs an OpenXR-runtime probe first (does Horizon Link forward it? does SteamVR over Link reserve it?) and a new action-map entry. Worth a 1-day spike after the Comfort tab lands. Track as roadmap item #12.
The reasoning, in one sentence: our MesselpitMenuTool is already the
right architecture; the gap is content (locomotion / render settings),
not plumbing.
- Is there an internal-but-unexposed registry inside
XRMenuToolfor appending widgets? The forum results and docs say no, but I can't rule out an undocumented_register_settings_widget()hook in the Kit source. Worth a 10-minute grep inomni.kit.xr.ui.stage/,omni.kit.xr.advanced_settings_ui/, andomni.kit.xr.system.openxr/before committing to option (a). If one exists, option (b) becomes feasible and we could ship a single-button experience. - Does Kit 110.x or 111 add an XR-menu extension API? I couldn't find an XR-specific changelog in the search results. The Kit changelog index would be worth scanning if a deeper investigation is justified.
- Does Horizon Link's OpenXR runtime expose
/input/menu/clickto apps when using Quest 3 Touch Plus? This is the prerequisite for the deferred ≡-button option. Easiest answer: bind it in a test action map and see if events fire.
Omniverse / Kit:
- Input & Interaction — Omniverse Spatial Docs
- XR Core events, action maps and eventgenerators for modifying controls (NVIDIA Developer Forum)
- Customize UI design for VR experience (NVIDIA Developer Forum)
NVIDIA-Omniverse/kit-xr-samples(GitHub)- Omniverse Spatial XR Settings Reference
VR ecosystem:
- Tech Note: Touch Button Mapping Best Practices (Meta)
- How to use Meta Quest Touch Plus controllers
- Wrist buttons (Meta Horizon design)
- Controller Input Mapping (Meta / Unreal)
- VR Template in Unreal Engine
- Half-Life Alyx in-game menu (Steam community)
- Unity XR Input docs
- How to detect Universal Menu through OpenXR? (Unity discussion)
- Interaction in OpenXR (Khronos class)
- SteamVR Developers FAQ