mirror of
https://gitlab.freedesktop.org/monado/monado.git
synced 2025-01-19 21:28:50 +00:00
a/bindings: Interaction profile inheritance
A requirement of interaction profile (extensions) specify that some/all actions must be supported by all other profiles. This commit modifies the binding generation to support data-inheritance in bindings.json: * Adds support for profiles in bindings.json to inherit & override other profiles * Adds a new concept of virtual profiles for profile like extensions (e.g. `XR_EXT_palm_pose`) which do not define a profile themselves but require their newly defined actions to be supported by all profiles. * Generates verify bindings functions which only check extensions actions only if the extension is enabled.
This commit is contained in:
parent
258357489c
commit
0b410a7119
|
@ -1,11 +1,33 @@
|
||||||
{
|
{
|
||||||
"$schema": "./bindings.schema.json",
|
"$schema": "./bindings.schema.json",
|
||||||
"profiles": {
|
"profiles": {
|
||||||
|
"/virtual_profiles/ext/palm_pose": {
|
||||||
|
"title": "Ext Palm Pose",
|
||||||
|
"type": "tracked_controller",
|
||||||
|
"extension": "XR_EXT_palm_pose",
|
||||||
|
"monado_device": "XRT_DEVICE_SIMPLE_CONTROLLER",
|
||||||
|
"subaction_paths": [
|
||||||
|
"/user/hand/left",
|
||||||
|
"/user/hand/right"
|
||||||
|
],
|
||||||
|
"subpaths": {
|
||||||
|
"/input/palm_ext": {
|
||||||
|
"type": "pose",
|
||||||
|
"localized_name": "Palm Pose",
|
||||||
|
"components": ["pose"],
|
||||||
|
"monado_bindings": {
|
||||||
|
"pose": "XRT_INPUT_GENERIC_PALM_POSE"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
"/interaction_profiles/khr/simple_controller": {
|
"/interaction_profiles/khr/simple_controller": {
|
||||||
"title": "Khronos Simple Controller",
|
"title": "Khronos Simple Controller",
|
||||||
"type": "tracked_controller",
|
"type": "tracked_controller",
|
||||||
"steamvr_controllertype": "khr_simple_controller",
|
"steamvr_controllertype": "khr_simple_controller",
|
||||||
"monado_device": "XRT_DEVICE_SIMPLE_CONTROLLER",
|
"monado_device": "XRT_DEVICE_SIMPLE_CONTROLLER",
|
||||||
|
"extended_by": ["ext/palm_pose"],
|
||||||
"subaction_paths": [
|
"subaction_paths": [
|
||||||
"/user/hand/left",
|
"/user/hand/left",
|
||||||
"/user/hand/right"
|
"/user/hand/right"
|
||||||
|
@ -58,6 +80,7 @@
|
||||||
"title": "Google Daydream Controller",
|
"title": "Google Daydream Controller",
|
||||||
"type": "tracked_controller",
|
"type": "tracked_controller",
|
||||||
"monado_device": "XRT_DEVICE_DAYDREAM",
|
"monado_device": "XRT_DEVICE_DAYDREAM",
|
||||||
|
"extended_by": ["ext/palm_pose"],
|
||||||
"subaction_paths": [
|
"subaction_paths": [
|
||||||
"/user/hand/left",
|
"/user/hand/left",
|
||||||
"/user/hand/right"
|
"/user/hand/right"
|
||||||
|
@ -110,6 +133,7 @@
|
||||||
"type": "tracked_controller",
|
"type": "tracked_controller",
|
||||||
"steamvr_controllertype": "vive_controller",
|
"steamvr_controllertype": "vive_controller",
|
||||||
"monado_device": "XRT_DEVICE_VIVE_WAND",
|
"monado_device": "XRT_DEVICE_VIVE_WAND",
|
||||||
|
"extended_by": ["ext/palm_pose"],
|
||||||
"subaction_paths": [
|
"subaction_paths": [
|
||||||
"/user/hand/left",
|
"/user/hand/left",
|
||||||
"/user/hand/right"
|
"/user/hand/right"
|
||||||
|
@ -196,6 +220,7 @@
|
||||||
"type": "tracked_hmd",
|
"type": "tracked_hmd",
|
||||||
"steamvr_controllertype": "vive_pro",
|
"steamvr_controllertype": "vive_pro",
|
||||||
"monado_device": "XRT_DEVICE_VIVE_PRO",
|
"monado_device": "XRT_DEVICE_VIVE_PRO",
|
||||||
|
"extended_by": ["ext/palm_pose"],
|
||||||
"subaction_paths": [
|
"subaction_paths": [
|
||||||
"/user/head"
|
"/user/head"
|
||||||
],
|
],
|
||||||
|
@ -240,6 +265,7 @@
|
||||||
"type": "tracked_controller",
|
"type": "tracked_controller",
|
||||||
"steamvr_controllertype": "holographic_controller",
|
"steamvr_controllertype": "holographic_controller",
|
||||||
"monado_device": "XRT_DEVICE_WMR_CONTROLLER",
|
"monado_device": "XRT_DEVICE_WMR_CONTROLLER",
|
||||||
|
"extended_by": ["ext/palm_pose"],
|
||||||
"subaction_paths": [
|
"subaction_paths": [
|
||||||
"/user/hand/left",
|
"/user/hand/left",
|
||||||
"/user/hand/right"
|
"/user/hand/right"
|
||||||
|
@ -329,6 +355,7 @@
|
||||||
"type": "untracked_controller",
|
"type": "untracked_controller",
|
||||||
"steamvr_controllertype": "gamepad",
|
"steamvr_controllertype": "gamepad",
|
||||||
"monado_device": "XRT_DEVICE_XBOX_CONTROLLER",
|
"monado_device": "XRT_DEVICE_XBOX_CONTROLLER",
|
||||||
|
"extended_by": ["ext/palm_pose"],
|
||||||
"subaction_paths": [
|
"subaction_paths": [
|
||||||
"/user/gamepad"
|
"/user/gamepad"
|
||||||
],
|
],
|
||||||
|
@ -510,6 +537,7 @@
|
||||||
"title": "Oculus Go Controller",
|
"title": "Oculus Go Controller",
|
||||||
"type": "untracked_controller",
|
"type": "untracked_controller",
|
||||||
"monado_device": "XRT_DEVICE_GO_CONTROLLER",
|
"monado_device": "XRT_DEVICE_GO_CONTROLLER",
|
||||||
|
"extended_by": ["ext/palm_pose"],
|
||||||
"subaction_paths": [
|
"subaction_paths": [
|
||||||
"/user/hand/left",
|
"/user/hand/left",
|
||||||
"/user/hand/right"
|
"/user/hand/right"
|
||||||
|
@ -578,6 +606,7 @@
|
||||||
"type": "tracked_controller",
|
"type": "tracked_controller",
|
||||||
"steamvr_controllertype": "oculus_touch",
|
"steamvr_controllertype": "oculus_touch",
|
||||||
"monado_device": "XRT_DEVICE_TOUCH_CONTROLLER",
|
"monado_device": "XRT_DEVICE_TOUCH_CONTROLLER",
|
||||||
|
"extended_by": ["ext/palm_pose"],
|
||||||
"subaction_paths": [
|
"subaction_paths": [
|
||||||
"/user/hand/left",
|
"/user/hand/left",
|
||||||
"/user/hand/right"
|
"/user/hand/right"
|
||||||
|
@ -712,6 +741,7 @@
|
||||||
"type": "tracked_controller",
|
"type": "tracked_controller",
|
||||||
"steamvr_controllertype": "knuckles",
|
"steamvr_controllertype": "knuckles",
|
||||||
"monado_device": "XRT_DEVICE_INDEX_CONTROLLER",
|
"monado_device": "XRT_DEVICE_INDEX_CONTROLLER",
|
||||||
|
"extended_by": ["ext/palm_pose"],
|
||||||
"subaction_paths": [
|
"subaction_paths": [
|
||||||
"/user/hand/left",
|
"/user/hand/left",
|
||||||
"/user/hand/right"
|
"/user/hand/right"
|
||||||
|
@ -825,6 +855,7 @@
|
||||||
"type": "tracked_controller",
|
"type": "tracked_controller",
|
||||||
"monado_device": "XRT_DEVICE_HP_REVERB_G2_CONTROLLER",
|
"monado_device": "XRT_DEVICE_HP_REVERB_G2_CONTROLLER",
|
||||||
"extension": "XR_EXT_hp_mixed_reality_controller",
|
"extension": "XR_EXT_hp_mixed_reality_controller",
|
||||||
|
"extended_by": ["ext/palm_pose"],
|
||||||
"subaction_paths": [
|
"subaction_paths": [
|
||||||
"/user/hand/left",
|
"/user/hand/left",
|
||||||
"/user/hand/right"
|
"/user/hand/right"
|
||||||
|
@ -935,6 +966,7 @@
|
||||||
"type": "tracked_controller",
|
"type": "tracked_controller",
|
||||||
"monado_device": "XRT_DEVICE_SAMSUNG_ODYSSEY_CONTROLLER",
|
"monado_device": "XRT_DEVICE_SAMSUNG_ODYSSEY_CONTROLLER",
|
||||||
"extension": "XR_EXT_samsung_odyssey_controller",
|
"extension": "XR_EXT_samsung_odyssey_controller",
|
||||||
|
"extended_by": ["ext/palm_pose"],
|
||||||
"subaction_paths": [
|
"subaction_paths": [
|
||||||
"/user/hand/left",
|
"/user/hand/left",
|
||||||
"/user/hand/right"
|
"/user/hand/right"
|
||||||
|
@ -1024,6 +1056,7 @@
|
||||||
"type": "tracked_controller",
|
"type": "tracked_controller",
|
||||||
"monado_device": "XRT_DEVICE_ML2_CONTROLLER",
|
"monado_device": "XRT_DEVICE_ML2_CONTROLLER",
|
||||||
"extension": "XR_ML_ml2_controller_interaction",
|
"extension": "XR_ML_ml2_controller_interaction",
|
||||||
|
"extended_by": ["ext/palm_pose"],
|
||||||
"subaction_paths": [
|
"subaction_paths": [
|
||||||
"/user/hand/left",
|
"/user/hand/left",
|
||||||
"/user/hand/right"
|
"/user/hand/right"
|
||||||
|
@ -1110,6 +1143,7 @@
|
||||||
"type": "tracked_controller",
|
"type": "tracked_controller",
|
||||||
"monado_device": "XRT_DEVICE_HAND_INTERACTION",
|
"monado_device": "XRT_DEVICE_HAND_INTERACTION",
|
||||||
"extension": "XR_MSFT_hand_interaction",
|
"extension": "XR_MSFT_hand_interaction",
|
||||||
|
"extended_by": ["ext/palm_pose"],
|
||||||
"subaction_paths": [
|
"subaction_paths": [
|
||||||
"/user/hand/left",
|
"/user/hand/left",
|
||||||
"/user/hand/right"
|
"/user/hand/right"
|
||||||
|
@ -1155,6 +1189,7 @@
|
||||||
"type": "tracked_controller",
|
"type": "tracked_controller",
|
||||||
"monado_device": "XRT_DEVICE_PSMV",
|
"monado_device": "XRT_DEVICE_PSMV",
|
||||||
"extension": "XR_MNDX_ball_on_a_stick_controller",
|
"extension": "XR_MNDX_ball_on_a_stick_controller",
|
||||||
|
"extended_by": ["ext/palm_pose"],
|
||||||
"subaction_paths": [
|
"subaction_paths": [
|
||||||
"/user/hand/left",
|
"/user/hand/left",
|
||||||
"/user/hand/right"
|
"/user/hand/right"
|
||||||
|
@ -1280,6 +1315,7 @@
|
||||||
"type": "tracked_controller",
|
"type": "tracked_controller",
|
||||||
"monado_device": "XRT_DEVICE_HYDRA",
|
"monado_device": "XRT_DEVICE_HYDRA",
|
||||||
"extension": "XR_MNDX_hydra",
|
"extension": "XR_MNDX_hydra",
|
||||||
|
"extended_by": ["ext/palm_pose"],
|
||||||
"subaction_paths": [
|
"subaction_paths": [
|
||||||
"/user/hand/left",
|
"/user/hand/left",
|
||||||
"/user/hand/right"
|
"/user/hand/right"
|
||||||
|
@ -1362,6 +1398,7 @@
|
||||||
"type": "eye_tracker",
|
"type": "eye_tracker",
|
||||||
"monado_device": "XRT_DEVICE_EYE_GAZE_INTERACTION",
|
"monado_device": "XRT_DEVICE_EYE_GAZE_INTERACTION",
|
||||||
"extension": "XR_EXT_eye_gaze_interaction",
|
"extension": "XR_EXT_eye_gaze_interaction",
|
||||||
|
"extended_by": ["ext/palm_pose"],
|
||||||
"subaction_paths": [
|
"subaction_paths": [
|
||||||
"/user/eyes_ext"
|
"/user/eyes_ext"
|
||||||
],
|
],
|
||||||
|
@ -1381,6 +1418,7 @@
|
||||||
"type": "tracked_controller",
|
"type": "tracked_controller",
|
||||||
"monado_device": "XRT_DEVICE_VIVE_TRACKER",
|
"monado_device": "XRT_DEVICE_VIVE_TRACKER",
|
||||||
"extension": "XR_HTCX_vive_tracker_interaction",
|
"extension": "XR_HTCX_vive_tracker_interaction",
|
||||||
|
"extended_by": ["ext/palm_pose"],
|
||||||
"subaction_paths": [
|
"subaction_paths": [
|
||||||
"/user/hand/left",
|
"/user/hand/left",
|
||||||
"/user/hand/right"
|
"/user/hand/right"
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# Copyright 2020-2022, Collabora, Ltd.
|
# Copyright 2020-2023, Collabora, Ltd.
|
||||||
# SPDX-License-Identifier: BSL-1.0
|
# SPDX-License-Identifier: BSL-1.0
|
||||||
"""Generate code from a JSON file describing interaction profiles and
|
"""Generate code from a JSON file describing interaction profiles and
|
||||||
bindings."""
|
bindings."""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
|
import copy
|
||||||
|
import itertools
|
||||||
|
|
||||||
|
|
||||||
def find_component_in_list_by_name(name, component_list, subaction_path=None, identifier_json_path=None):
|
def find_component_in_list_by_name(name, component_list, subaction_path=None, identifier_json_path=None):
|
||||||
|
@ -19,6 +21,7 @@ def find_component_in_list_by_name(name, component_list, subaction_path=None, id
|
||||||
return component
|
return component
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def steamvr_subpath_name(steamvr_path, subpath_type):
|
def steamvr_subpath_name(steamvr_path, subpath_type):
|
||||||
if subpath_type == "pose":
|
if subpath_type == "pose":
|
||||||
return steamvr_path.replace("/input/", "/pose/")
|
return steamvr_path.replace("/input/", "/pose/")
|
||||||
|
@ -31,6 +34,7 @@ def steamvr_subpath_name(steamvr_path, subpath_type):
|
||||||
|
|
||||||
return steamvr_path
|
return steamvr_path
|
||||||
|
|
||||||
|
|
||||||
class PathsByLengthCollector:
|
class PathsByLengthCollector:
|
||||||
"""Helper class to sort paths by length, useful for creating fast path
|
"""Helper class to sort paths by length, useful for creating fast path
|
||||||
validation functions.
|
validation functions.
|
||||||
|
@ -56,6 +60,7 @@ class PathsByLengthCollector:
|
||||||
ret[length] = list(set_per_length)
|
ret[length] = list(set_per_length)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def dpad_paths(identifier_path, center):
|
def dpad_paths(identifier_path, center):
|
||||||
paths = [
|
paths = [
|
||||||
identifier_path + "/dpad_up",
|
identifier_path + "/dpad_up",
|
||||||
|
@ -69,6 +74,7 @@ def dpad_paths(identifier_path, center):
|
||||||
|
|
||||||
return paths
|
return paths
|
||||||
|
|
||||||
|
|
||||||
class DPad:
|
class DPad:
|
||||||
"""Class holding per identifier information for dpad emulation."""
|
"""Class holding per identifier information for dpad emulation."""
|
||||||
|
|
||||||
|
@ -190,6 +196,9 @@ class Component:
|
||||||
|
|
||||||
return paths
|
return paths
|
||||||
|
|
||||||
|
def get_full_path(self):
|
||||||
|
return self.subaction_path + self.identifier_json_path + '/' + self.component_name
|
||||||
|
|
||||||
def is_input(self):
|
def is_input(self):
|
||||||
# only haptics is output so far, everything else is input
|
# only haptics is output so far, everything else is input
|
||||||
return self.component_name != "haptic"
|
return self.component_name != "haptic"
|
||||||
|
@ -219,7 +228,8 @@ class Identifier:
|
||||||
for subaction_path in json_subaction_paths: # /user/hand/*
|
for subaction_path in json_subaction_paths: # /user/hand/*
|
||||||
for json_sub_path_itm in json_subpaths.items(): # /input/*, /output/*
|
for json_sub_path_itm in json_subpaths.items(): # /input/*, /output/*
|
||||||
json_path = json_sub_path_itm[0] # /input/trackpad
|
json_path = json_sub_path_itm[0] # /input/trackpad
|
||||||
json_subpath = json_sub_path_itm[1] # json object associated with a subpath (type, localized_name, ...)
|
# json object associated with a subpath (type, localized_name, ...)
|
||||||
|
json_subpath = json_sub_path_itm[1]
|
||||||
|
|
||||||
# Oculus Touch a,b/x,y components only exist on one controller
|
# Oculus Touch a,b/x,y components only exist on one controller
|
||||||
if "side" in json_subpath and "/user/hand/" + json_subpath["side"] != subaction_path:
|
if "side" in json_subpath and "/user/hand/" + json_subpath["side"] != subaction_path:
|
||||||
|
@ -266,22 +276,25 @@ class Profile:
|
||||||
|
|
||||||
def __init__(self, profile_name, json_profile):
|
def __init__(self, profile_name, json_profile):
|
||||||
"""Construct an profile."""
|
"""Construct an profile."""
|
||||||
|
self.parent_profiles = set()
|
||||||
self.name = profile_name
|
self.name = profile_name
|
||||||
self.localized_name = json_profile['title']
|
self.localized_name = json_profile['title']
|
||||||
self.profile_type = json_profile["type"]
|
self.profile_type = json_profile["type"]
|
||||||
self.monado_device_enum = json_profile["monado_device"]
|
self.monado_device_enum = json_profile["monado_device"]
|
||||||
self.validation_func_name = profile_name.replace("/interaction_profiles/", "").replace("/", "_")
|
self.validation_func_name = Profile.__strip_profile_prefix(
|
||||||
|
profile_name).replace("/", "_")
|
||||||
|
self.extension_name = json_profile.get("extension")
|
||||||
|
self.extended_by = json_profile.get("extended_by")
|
||||||
|
if self.extended_by is None:
|
||||||
|
self.extended_by = []
|
||||||
|
self.is_virtual = profile_name.startswith("/virtual_profiles/")
|
||||||
self.identifiers = Identifier.parse_identifiers(json_profile)
|
self.identifiers = Identifier.parse_identifiers(json_profile)
|
||||||
|
|
||||||
self.steamvr_controller_type = None
|
self.steamvr_controller_type = None
|
||||||
if "steamvr_controllertype" in json_profile:
|
if "steamvr_controllertype" in json_profile:
|
||||||
self.steamvr_controller_type = json_profile["steamvr_controllertype"]
|
self.steamvr_controller_type = json_profile["steamvr_controllertype"]
|
||||||
|
|
||||||
self.components = []
|
self.__update_component_list()
|
||||||
for identifier in self.identifiers:
|
|
||||||
for component in identifier.components:
|
|
||||||
self.components.append(component)
|
|
||||||
|
|
||||||
collector = PathsByLengthCollector()
|
collector = PathsByLengthCollector()
|
||||||
for component in self.components:
|
for component in self.components:
|
||||||
collector.add_paths(component.get_full_openxr_paths())
|
collector.add_paths(component.get_full_openxr_paths())
|
||||||
|
@ -300,9 +313,60 @@ class Profile:
|
||||||
continue
|
continue
|
||||||
path = identifier.identifier_path
|
path = identifier.identifier_path
|
||||||
collector.add_paths(identifier.dpad.paths)
|
collector.add_paths(identifier.dpad.paths)
|
||||||
|
|
||||||
self.dpad_paths_by_length = collector.to_dict_of_lists()
|
self.dpad_paths_by_length = collector.to_dict_of_lists()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def __strip_profile_prefix(cls, profile_path):
|
||||||
|
return profile_path.replace("/interaction_profiles/", "").replace("/virtual_profiles/", "")
|
||||||
|
|
||||||
|
def is_parent_profile(self, child_profile):
|
||||||
|
if child_profile == self:
|
||||||
|
return False
|
||||||
|
if child_profile.extended_by is None:
|
||||||
|
return False
|
||||||
|
parent_path = Profile.__strip_profile_prefix(self.name)
|
||||||
|
return parent_path in child_profile.extended_by
|
||||||
|
|
||||||
|
def merge_parent_profiles(self):
|
||||||
|
self.identifiers = self.__get_merged_identifiers_helper({}).values()
|
||||||
|
self.__update_component_list()
|
||||||
|
|
||||||
|
def __get_merged_identifiers_helper(self, identifier_map):
|
||||||
|
for ident in self.identifiers:
|
||||||
|
if ident.identifier_path not in identifier_map:
|
||||||
|
identifier_map[ident.identifier_path] = copy.deepcopy(ident)
|
||||||
|
continue
|
||||||
|
child_indent = identifier_map[ident.identifier_path]
|
||||||
|
if child_indent.dpad is None:
|
||||||
|
child_indent.dpad = ident.dpad
|
||||||
|
child_comps = child_indent.components
|
||||||
|
for parent_comp in ident.components:
|
||||||
|
parent_path = parent_comp.get_full_path()
|
||||||
|
child_exists = False
|
||||||
|
for child_comp in child_comps:
|
||||||
|
if child_comp.get_full_path() == parent_path:
|
||||||
|
child_exists = True
|
||||||
|
break
|
||||||
|
if not child_exists:
|
||||||
|
child_comps.append(parent_comp)
|
||||||
|
|
||||||
|
parent_profiles = self.parent_profiles
|
||||||
|
if parent_profiles is None or len(parent_profiles) == 0:
|
||||||
|
return identifier_map
|
||||||
|
else:
|
||||||
|
for parent in parent_profiles:
|
||||||
|
parent.__get_merged_identifiers_helper(identifier_map)
|
||||||
|
return identifier_map
|
||||||
|
|
||||||
|
def __update_component_list(self):
|
||||||
|
self.components = []
|
||||||
|
for identifier in self.identifiers:
|
||||||
|
for component in identifier.components:
|
||||||
|
self.components.append(component)
|
||||||
|
|
||||||
|
|
||||||
|
oxr_verify_extension_status_struct_name = "oxr_verify_extension_status"
|
||||||
|
|
||||||
|
|
||||||
class Bindings:
|
class Bindings:
|
||||||
"""A collection of interaction profiles used in bindings."""
|
"""A collection of interaction profiles used in bindings."""
|
||||||
|
@ -324,6 +388,50 @@ class Bindings:
|
||||||
"""Construct a bindings from a dictionary of profiles."""
|
"""Construct a bindings from a dictionary of profiles."""
|
||||||
self.profiles = [Profile(profile_name, json_profile) for
|
self.profiles = [Profile(profile_name, json_profile) for
|
||||||
profile_name, json_profile in json_root["profiles"].items()]
|
profile_name, json_profile in json_root["profiles"].items()]
|
||||||
|
self.__set_parent_profile_refs()
|
||||||
|
self.__mine_for_diamond_errors()
|
||||||
|
|
||||||
|
self.virtual_profiles = [p for p in self.profiles if p.is_virtual]
|
||||||
|
self.profiles = [p for p in self.profiles if not p.is_virtual]
|
||||||
|
for profile in self.profiles:
|
||||||
|
profile.merge_parent_profiles()
|
||||||
|
|
||||||
|
def __set_parent_profile_refs(self):
|
||||||
|
for profile1 in self.profiles:
|
||||||
|
for profile2 in self.profiles:
|
||||||
|
if profile1.is_parent_profile(profile2):
|
||||||
|
profile2.parent_profiles.add(profile1)
|
||||||
|
|
||||||
|
def __mine_for_diamond_errors(self):
|
||||||
|
for profile in self.profiles:
|
||||||
|
parent_path_set = []
|
||||||
|
if self.__has_diamonds(profile, parent_path_set):
|
||||||
|
msg = f"Interaction Profile: {profile.name} in bindings.json has a diamond hierarchy, this is not supported."
|
||||||
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
|
def __has_diamonds(self, profile, parent_path_set):
|
||||||
|
if profile.name in parent_path_set:
|
||||||
|
return True
|
||||||
|
parent_path_set.append(profile.name)
|
||||||
|
for parent in profile.parent_profiles:
|
||||||
|
if self.__has_diamonds(parent, parent_path_set):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def make_oxr_verify_extension_status_struct_str(self):
|
||||||
|
struct_str: str = f"struct {oxr_verify_extension_status_struct_name}{{\n"
|
||||||
|
ext_set = set()
|
||||||
|
for profile in itertools.chain(self.virtual_profiles, self.profiles):
|
||||||
|
ext_name = profile.extension_name
|
||||||
|
if ext_name is None or len(ext_name) == 0:
|
||||||
|
continue
|
||||||
|
if ext_name in ext_set:
|
||||||
|
continue
|
||||||
|
ext_set.add(ext_name)
|
||||||
|
ext_name = ext_name.replace("XR_", "")
|
||||||
|
struct_str += f"\tbool {ext_name};\n"
|
||||||
|
struct_str += "};\n"
|
||||||
|
return struct_str
|
||||||
|
|
||||||
|
|
||||||
header = '''// Copyright 2020-2022, Collabora, Ltd.
|
header = '''// Copyright 2020-2022, Collabora, Ltd.
|
||||||
|
@ -333,34 +441,81 @@ header = '''// Copyright 2020-2022, Collabora, Ltd.
|
||||||
* @brief {brief}.
|
* @brief {brief}.
|
||||||
* @author Jakob Bornecrantz <jakob@collabora.com>
|
* @author Jakob Bornecrantz <jakob@collabora.com>
|
||||||
* @author Christoph Haag <christoph.haag@collabora.com>
|
* @author Christoph Haag <christoph.haag@collabora.com>
|
||||||
|
* @author Korcan Hussein <korcan.hussein@collabora.com>
|
||||||
* @ingroup {group}
|
* @ingroup {group}
|
||||||
*/
|
*/
|
||||||
'''
|
'''
|
||||||
|
|
||||||
func_start = '''
|
func_start = '''
|
||||||
bool
|
bool
|
||||||
{name}(const char *str, size_t length)
|
{name}(const struct {ext_status_struct_name}* exts, const char *str, size_t length)
|
||||||
{{
|
{{
|
||||||
\tswitch (length) {{
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
if_strcmp = '''if (strcmp(str, "{check}") == 0) {{
|
|
||||||
\t\t\treturn true;
|
def write_verify_func_begin(f, name):
|
||||||
\t\t}} else '''
|
f.write(func_start.format(
|
||||||
|
name=name, ext_status_struct_name=oxr_verify_extension_status_struct_name))
|
||||||
|
|
||||||
|
|
||||||
def write_verify_func(f, name, dict_of_lists):
|
def write_verify_func_end(f):
|
||||||
|
f.write("\treturn false;\n}\n")
|
||||||
|
|
||||||
|
|
||||||
|
if_strcmp = '''{exttab}if (strcmp(str, "{check}") == 0) {{
|
||||||
|
{exttab}\t\t\treturn true;
|
||||||
|
{exttab}\t\t}} else '''
|
||||||
|
|
||||||
|
|
||||||
|
def write_verify_func_switch(f, dict_of_lists, profile_name, ext_name):
|
||||||
"""Generate function to check if a string is in a set of strings.
|
"""Generate function to check if a string is in a set of strings.
|
||||||
Input is a file to write the code into, a dict where keys are length and
|
Input is a file to write the code into, a dict where keys are length and
|
||||||
the values are lists of strings of that length. And a suffix if any."""
|
the values are lists of strings of that length. And a suffix if any."""
|
||||||
|
if len(dict_of_lists) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
f.write(func_start.format(name=name))
|
f.write(f"\t// generated from: {profile_name}\n")
|
||||||
|
is_ext = ext_name is not None and len(ext_name) > 0
|
||||||
|
ext_tab = ""
|
||||||
|
if is_ext:
|
||||||
|
ext_name = ext_name.replace("XR_", "")
|
||||||
|
f.write(f"\tif (exts->{ext_name}) {{\n")
|
||||||
|
ext_tab = "\t"
|
||||||
|
|
||||||
|
f.write(f"{ext_tab}\tswitch (length) {{\n")
|
||||||
for length in dict_of_lists:
|
for length in dict_of_lists:
|
||||||
f.write("\tcase " + str(length) + ":\n\t\t")
|
f.write(f"{ext_tab}\tcase {str(length)}:\n\t\t")
|
||||||
for path in dict_of_lists[length]:
|
for path in dict_of_lists[length]:
|
||||||
f.write(if_strcmp.format(check=path))
|
f.write(if_strcmp.format(exttab=ext_tab, check=path))
|
||||||
f.write("{\n\t\t\treturn false;\n\t\t}\n")
|
f.write(f"{ext_tab}{{\n{ext_tab}\t\t\tbreak;\n{ext_tab}\t\t}}\n")
|
||||||
f.write("\tdefault:\n\t\treturn false;\n\t}\n}\n")
|
f.write(f"{ext_tab}\tdefault: break;\n{ext_tab}\t}}\n")
|
||||||
|
|
||||||
|
if is_ext:
|
||||||
|
f.write("\t}\n")
|
||||||
|
|
||||||
|
|
||||||
|
def write_verify_func_body(f, profile, dict_name):
|
||||||
|
if profile is None or dict_name is None or len(dict_name) == 0:
|
||||||
|
return
|
||||||
|
write_verify_func_switch(f, getattr(
|
||||||
|
profile, dict_name), profile.name, profile.extension_name)
|
||||||
|
if profile.parent_profiles is None:
|
||||||
|
return
|
||||||
|
for pp in profile.parent_profiles:
|
||||||
|
write_verify_func_body(f, pp, dict_name)
|
||||||
|
|
||||||
|
|
||||||
|
def write_verify_func(f, profile, dict_name, suffix):
|
||||||
|
write_verify_func_begin(
|
||||||
|
f, f"oxr_verify_{profile.validation_func_name}{suffix}")
|
||||||
|
write_verify_func_body(f, profile, dict_name)
|
||||||
|
write_verify_func_end(f)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_verify_functions(f, profile):
|
||||||
|
write_verify_func(f, profile, "subpaths_by_length", "_subpath")
|
||||||
|
write_verify_func(f, profile, "dpad_paths_by_length", "_dpad_path")
|
||||||
|
write_verify_func(f, profile, "dpad_emulators_by_length", "_dpad_emulator")
|
||||||
|
|
||||||
|
|
||||||
def generate_bindings_c(file, p):
|
def generate_bindings_c(file, p):
|
||||||
|
@ -375,12 +530,7 @@ def generate_bindings_c(file, p):
|
||||||
''')
|
''')
|
||||||
|
|
||||||
for profile in p.profiles:
|
for profile in p.profiles:
|
||||||
name = "oxr_verify_" + profile.validation_func_name + "_subpath"
|
generate_verify_functions(f, profile)
|
||||||
write_verify_func(f, name, profile.subpaths_by_length)
|
|
||||||
name = "oxr_verify_" + profile.validation_func_name + "_dpad_path"
|
|
||||||
write_verify_func(f, name, profile.dpad_paths_by_length)
|
|
||||||
name = "oxr_verify_" + profile.validation_func_name + "_dpad_emulator"
|
|
||||||
write_verify_func(f, name, profile.dpad_emulators_by_length)
|
|
||||||
|
|
||||||
f.write(
|
f.write(
|
||||||
f'\n\nstruct profile_template profile_templates[{len(p.profiles)}] = {{ // array of profile_template\n')
|
f'\n\nstruct profile_template profile_templates[{len(p.profiles)}] = {{ // array of profile_template\n')
|
||||||
|
@ -404,7 +554,8 @@ def generate_bindings_c(file, p):
|
||||||
component: Component
|
component: Component
|
||||||
for idx, component in enumerate(profile.components):
|
for idx, component in enumerate(profile.components):
|
||||||
|
|
||||||
steamvr_path = component.steamvr_path # @todo Doesn't handle pose yet.
|
# @todo Doesn't handle pose yet.
|
||||||
|
steamvr_path = component.steamvr_path
|
||||||
if component.component_name in ["click", "touch", "force", "value"]:
|
if component.component_name in ["click", "touch", "force", "value"]:
|
||||||
steamvr_path += "/" + component.component_name
|
steamvr_path += "/" + component.component_name
|
||||||
|
|
||||||
|
@ -566,13 +717,14 @@ def generate_bindings_h(file, p):
|
||||||
// clang-format off
|
// clang-format off
|
||||||
''')
|
''')
|
||||||
|
|
||||||
|
oxr_verify_struct_str = p.make_oxr_verify_extension_status_struct_str()
|
||||||
|
f.write(oxr_verify_struct_str)
|
||||||
|
|
||||||
|
fn_prefixes = ["_subpath", "_dpad_path", "_dpad_emulator"]
|
||||||
for profile in p.profiles:
|
for profile in p.profiles:
|
||||||
f.write("\nbool\noxr_verify_" + profile.validation_func_name +
|
for fn_suffix in fn_prefixes:
|
||||||
"_subpath(const char *str, size_t length);\n")
|
f.write(
|
||||||
f.write("\nbool\noxr_verify_" + profile.validation_func_name +
|
f"\nbool\noxr_verify_{profile.validation_func_name}{fn_suffix}(const struct {oxr_verify_extension_status_struct_name}* extensions, const char *str, size_t length);\n")
|
||||||
"_dpad_path(const char *str, size_t length);\n")
|
|
||||||
f.write("\nbool\noxr_verify_" + profile.validation_func_name +
|
|
||||||
"_dpad_emulator(const char *str, size_t length);\n")
|
|
||||||
|
|
||||||
f.write(f'''
|
f.write(f'''
|
||||||
#define PATHS_PER_BINDING_TEMPLATE 16
|
#define PATHS_PER_BINDING_TEMPLATE 16
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
// Copyright 2019-2022, Collabora, Ltd.
|
// Copyright 2019-2023, Collabora, Ltd.
|
||||||
// SPDX-License-Identifier: BSL-1.0
|
// SPDX-License-Identifier: BSL-1.0
|
||||||
/*!
|
/*!
|
||||||
* @file
|
* @file
|
||||||
* @brief Action related API entrypoint functions.
|
* @brief Action related API entrypoint functions.
|
||||||
* @author Jakob Bornecrantz <jakob@collabora.com>
|
* @author Jakob Bornecrantz <jakob@collabora.com>
|
||||||
|
* @author Korcan Hussein <korcan.hussein@collabora.com>
|
||||||
* @ingroup oxr_api
|
* @ingroup oxr_api
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -25,7 +26,7 @@
|
||||||
#include "bindings/b_generated_bindings.h"
|
#include "bindings/b_generated_bindings.h"
|
||||||
|
|
||||||
|
|
||||||
typedef bool (*path_verify_fn_t)(const char *, size_t);
|
typedef bool (*path_verify_fn_t)(const struct oxr_verify_extension_status *, const char *, size_t);
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -41,6 +42,7 @@ process_dpad(struct oxr_logger *log,
|
||||||
struct oxr_dpad_state *state,
|
struct oxr_dpad_state *state,
|
||||||
const XrInteractionProfileDpadBindingEXT *dpad,
|
const XrInteractionProfileDpadBindingEXT *dpad,
|
||||||
path_verify_fn_t dpad_emulator_fn,
|
path_verify_fn_t dpad_emulator_fn,
|
||||||
|
const struct oxr_verify_extension_status *verify_ext_status,
|
||||||
const char *prefix,
|
const char *prefix,
|
||||||
const char *ip_str)
|
const char *ip_str)
|
||||||
{
|
{
|
||||||
|
@ -54,7 +56,7 @@ process_dpad(struct oxr_logger *log,
|
||||||
dpad->binding);
|
dpad->binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dpad_emulator_fn(str, length)) {
|
if (!dpad_emulator_fn(verify_ext_status, str, length)) {
|
||||||
return oxr_error(log, XR_ERROR_PATH_UNSUPPORTED,
|
return oxr_error(log, XR_ERROR_PATH_UNSUPPORTED,
|
||||||
"(%s->binding == \"%s\") is not a valid dpad binding path for profile \"%s\"", prefix,
|
"(%s->binding == \"%s\") is not a valid dpad binding path for profile \"%s\"", prefix,
|
||||||
str, ip_str);
|
str, ip_str);
|
||||||
|
@ -313,6 +315,23 @@ oxr_xrSuggestInteractionProfileBindings(XrInstance instance,
|
||||||
// Needed in various paths here.
|
// Needed in various paths here.
|
||||||
const char *str = NULL;
|
const char *str = NULL;
|
||||||
size_t length;
|
size_t length;
|
||||||
|
const struct oxr_verify_extension_status verify_ext_status = {
|
||||||
|
#ifdef OXR_HAVE_EXT_palm_pose
|
||||||
|
.EXT_palm_pose = inst->extensions.EXT_palm_pose,
|
||||||
|
#endif
|
||||||
|
.EXT_hp_mixed_reality_controller = inst->extensions.EXT_hp_mixed_reality_controller,
|
||||||
|
.EXT_samsung_odyssey_controller = inst->extensions.EXT_samsung_odyssey_controller,
|
||||||
|
.ML_ml2_controller_interaction = inst->extensions.ML_ml2_controller_interaction,
|
||||||
|
#ifdef OXR_HAVE_MSFT_hand_interaction
|
||||||
|
.MSFT_hand_interaction = inst->extensions.MSFT_hand_interaction,
|
||||||
|
#endif
|
||||||
|
.MNDX_ball_on_a_stick_controller = inst->extensions.MNDX_ball_on_a_stick_controller,
|
||||||
|
.MNDX_hydra = inst->extensions.MNDX_hydra,
|
||||||
|
.EXT_eye_gaze_interaction = inst->extensions.EXT_eye_gaze_interaction,
|
||||||
|
#ifdef OXR_HAVE_HTCX_vive_tracker_interaction
|
||||||
|
.HTCX_vive_tracker_interaction = inst->extensions.HTCX_vive_tracker_interaction,
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
for (size_t i = 0; i < suggestedBindings->countSuggestedBindings; i++) {
|
for (size_t i = 0; i < suggestedBindings->countSuggestedBindings; i++) {
|
||||||
const XrActionSuggestedBinding *s = &suggestedBindings->suggestedBindings[i];
|
const XrActionSuggestedBinding *s = &suggestedBindings->suggestedBindings[i];
|
||||||
|
@ -335,12 +354,12 @@ oxr_xrSuggestInteractionProfileBindings(XrInstance instance,
|
||||||
i, s->binding);
|
i, s->binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subpath_fn(str, length)) {
|
if (subpath_fn(&verify_ext_status, str, length)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef XR_EXT_dpad_binding
|
#ifdef XR_EXT_dpad_binding
|
||||||
if (dpad_path_fn(str, length)) {
|
if (dpad_path_fn(&verify_ext_status, str, length)) {
|
||||||
if (!has_dpad) {
|
if (!has_dpad) {
|
||||||
return oxr_error(
|
return oxr_error(
|
||||||
&log, XR_ERROR_PATH_UNSUPPORTED,
|
&log, XR_ERROR_PATH_UNSUPPORTED,
|
||||||
|
@ -387,7 +406,8 @@ oxr_xrSuggestInteractionProfileBindings(XrInstance instance,
|
||||||
"XrInteractionProfileDpadBindingEXT>",
|
"XrInteractionProfileDpadBindingEXT>",
|
||||||
i);
|
i);
|
||||||
|
|
||||||
ret = process_dpad(&log, inst, &dpad_state, dpad, dpad_emulator_fn, temp, ip_str);
|
ret = process_dpad(&log, inst, &dpad_state, dpad, dpad_emulator_fn, &verify_ext_status, temp,
|
||||||
|
ip_str);
|
||||||
if (ret != XR_SUCCESS) {
|
if (ret != XR_SUCCESS) {
|
||||||
// Teardown the state.
|
// Teardown the state.
|
||||||
oxr_dpad_state_deinit(&dpad_state);
|
oxr_dpad_state_deinit(&dpad_state);
|
||||||
|
|
Loading…
Reference in a new issue