The open source OpenXR runtime
at main 566 lines 15 kB view raw
1// Copyright 2019, Collabora, Ltd. 2// SPDX-License-Identifier: BSL-1.0 3/*! 4 * @file 5 * @brief Code to manage the settings file. 6 * @author Jakob Bornecrantz <jakob@collabora.com> 7 * @ingroup st_prober 8 */ 9 10#include <xrt/xrt_device.h> 11#include "xrt/xrt_settings.h" 12#include "xrt/xrt_config.h" 13 14#include "util/u_file.h" 15#include "util/u_json.h" 16#include "util/u_debug.h" 17#include "util/u_pretty_print.h" 18 19#include "u_config_json.h" 20 21#include <stdio.h> 22#include <stdlib.h> 23#include <string.h> 24#include <sys/stat.h> 25 26#include "bindings/b_generated_bindings_helpers.h" 27#include <assert.h> 28 29DEBUG_GET_ONCE_OPTION(active_config, "P_OVERRIDE_ACTIVE_CONFIG", NULL) 30 31#define CONFIG_FILE_NAME "config_v0.json" 32#define GUI_STATE_FILE_NAME "gui_state_v0.json" 33 34void 35u_config_json_close(struct u_config_json *json) 36{ 37 if (json->root != NULL) { 38 cJSON_Delete(json->root); 39 json->root = NULL; 40 } 41 json->file_loaded = false; 42} 43 44static void 45u_config_json_open_or_create_file(struct u_config_json *json, const char *filename) 46{ 47 json->file_loaded = false; 48#if (defined(XRT_OS_LINUX) || defined(XRT_OS_WINDOWS)) && !defined(XRT_OS_ANDROID) 49 char tmp[1024]; 50 ssize_t ret = u_file_get_path_in_config_dir(filename, tmp, sizeof(tmp)); 51 if (ret <= 0) { 52 U_LOG_E( 53 "Could not load or create config file no $HOME " 54 "or $XDG_CONFIG_HOME env variables defined"); 55 return; 56 } 57 58 FILE *file = u_file_open_file_in_config_dir(filename, "rb"); 59 if (file == NULL) { 60 return; 61 } 62 63 json->file_loaded = true; 64 65 char *str = u_file_read_content(file, NULL); 66 fclose(file); 67 if (str == NULL) { 68 U_LOG_E("Could not read the contents of '%s'!", tmp); 69 return; 70 } 71 72 // No config created, ignore. 73 if (strlen(str) == 0) { 74 free(str); 75 return; 76 } 77 78 json->root = cJSON_Parse(str); 79 if (json->root == NULL) { 80 U_LOG_E("Failed to parse JSON in '%s':\n%s\n#######", tmp, str); 81 U_LOG_E("'%s'", cJSON_GetErrorPtr()); 82 } 83 84 free(str); 85#else 86 //! @todo implement the underlying u_file_get_path_in_config_dir 87 return; 88#endif 89} 90 91void 92u_config_json_open_or_create_main_file(struct u_config_json *json) 93{ 94 u_config_json_open_or_create_file(json, CONFIG_FILE_NAME); 95} 96 97static cJSON * 98get_obj(cJSON *json, const char *name) 99{ 100 cJSON *item = cJSON_GetObjectItemCaseSensitive(json, name); 101 if (item == NULL) { 102 U_LOG_I("JSON does not contain node '%s'!", name); 103 } 104 return item; 105} 106 107XRT_MAYBE_UNUSED static bool 108get_obj_bool(cJSON *json, const char *name, bool *out_bool) 109{ 110 if (json == NULL) { 111 return false; 112 } 113 cJSON *item = get_obj(json, name); 114 if (item == NULL) { 115 return false; 116 } 117 118 if (!u_json_get_bool(item, out_bool)) { 119 U_LOG_E("Failed to parse '%s'!", name); 120 return false; 121 } 122 123 return true; 124} 125 126static bool 127get_obj_int(cJSON *json, const char *name, int *out_int) 128{ 129 if (json == NULL) { 130 return false; 131 } 132 cJSON *item = get_obj(json, name); 133 if (item == NULL) { 134 return false; 135 } 136 137 if (!u_json_get_int(item, out_int)) { 138 U_LOG_E("Failed to parse '%s'!", name); 139 return false; 140 } 141 142 return true; 143} 144 145static bool 146get_obj_float(cJSON *json, const char *name, float *out_float) 147{ 148 if (json == NULL) { 149 return false; 150 } 151 cJSON *item = get_obj(json, name); 152 if (item == NULL) { 153 return false; 154 } 155 156 if (!u_json_get_float(item, out_float)) { 157 U_LOG_E("Failed to parse '%s'!", name); 158 return false; 159 } 160 161 return true; 162} 163 164static bool 165get_obj_str(cJSON *json, const char *name, char *array, size_t array_size) 166{ 167 if (json == NULL) { 168 return false; 169 } 170 cJSON *item = get_obj(json, name); 171 if (item == NULL) { 172 return false; 173 } 174 175 if (!u_json_get_string_into_array(item, array, array_size)) { 176 U_LOG_E("Failed to parse '%s'!", name); 177 return false; 178 } 179 180 return true; 181} 182 183static bool 184is_json_ok(struct u_config_json *json) 185{ 186 if (json->root == NULL) { 187 if (json->file_loaded) { 188 U_LOG_E("Config file was loaded but JSON is not parsed!"); 189 } else { 190 U_LOG_I("No config file was loaded!"); 191 } 192 return false; 193 } 194 195 return true; 196} 197 198static void 199u_config_json_assign_schema(struct u_config_json *json) 200{ 201 cJSON_DeleteItemFromObject(json->root, "$schema"); 202 cJSON_AddStringToObject(json->root, "$schema", 203 "https://monado.pages.freedesktop.org/monado/config_v0.schema.json"); 204} 205 206static bool 207parse_active(const char *str, const char *from, enum u_config_json_active_config *out_active) 208{ 209 if (strcmp(str, "none") == 0) { 210 *out_active = U_ACTIVE_CONFIG_NONE; 211 } else if (strcmp(str, "tracking") == 0) { 212 *out_active = U_ACTIVE_CONFIG_TRACKING; 213 } else if (strcmp(str, "remote") == 0) { 214 *out_active = U_ACTIVE_CONFIG_REMOTE; 215 } else { 216 U_LOG_E("Unknown active config '%s' from %s.", str, from); 217 *out_active = U_ACTIVE_CONFIG_NONE; 218 return false; 219 } 220 221 return true; 222} 223 224void 225u_config_json_get_active(struct u_config_json *json, enum u_config_json_active_config *out_active) 226{ 227 const char *str = debug_get_option_active_config(); 228 if (str != NULL && parse_active(str, "environment", out_active)) { 229 return; 230 } 231 232 char tmp[256]; 233 if (!is_json_ok(json) || !get_obj_str(json->root, "active", tmp, sizeof(tmp))) { 234 *out_active = U_ACTIVE_CONFIG_NONE; 235 return; 236 } 237 238 parse_active(tmp, "json", out_active); 239} 240 241bool 242u_config_json_get_remote_settings(struct u_config_json *json, int *out_port, uint32_t *out_view_count) 243{ 244 cJSON *t = cJSON_GetObjectItemCaseSensitive(json->root, "remote"); 245 if (t == NULL) { 246 U_LOG_E("No remote node"); 247 return false; 248 } 249 250 int ver = -1; 251 if (!get_obj_int(t, "version", &ver)) { 252 U_LOG_E("Missing version tag!"); 253 return false; 254 } 255 if (ver >= 1) { 256 U_LOG_E("Unknown version tag '%i'!", ver); 257 return false; 258 } 259 260 int port = 0; 261 if (!get_obj_int(t, "port", &port)) { 262 return false; 263 } 264 int view_count = 0; 265 if (!get_obj_int(t, "view_count", &view_count)) { 266 return false; 267 } 268 269 *out_port = port; 270 *out_view_count = view_count; 271 272 return true; 273} 274 275static cJSON * 276open_tracking_settings(struct u_config_json *json) 277{ 278 if (!is_json_ok(json)) { 279 return NULL; 280 } 281 282 cJSON *t = cJSON_GetObjectItemCaseSensitive(json->root, "tracking"); 283 if (t == NULL) { 284 U_LOG_I("Config file does not contain tracking config"); 285 return NULL; 286 } 287 288 return t; 289} 290 291bool 292u_config_json_get_tracking_overrides(struct u_config_json *json, 293 struct xrt_tracking_override *out_overrides, 294 size_t *out_override_count) 295{ 296 cJSON *t = open_tracking_settings(json); 297 if (t == NULL) { 298 return false; 299 } 300 301 302 cJSON *overrides = cJSON_GetObjectItemCaseSensitive(t, "tracking_overrides"); 303 304 *out_override_count = 0; 305 306 cJSON *override = NULL; 307 cJSON_ArrayForEach(override, overrides) 308 { 309 bool bad = false; 310 311 struct xrt_tracking_override *o = &out_overrides[(*out_override_count)++]; 312 bad |= !get_obj_str(override, "target_device_serial", o->target_device_serial, XRT_DEVICE_NAME_LEN); 313 bad |= !get_obj_str(override, "tracker_device_serial", o->tracker_device_serial, XRT_DEVICE_NAME_LEN); 314 315 char override_type[256]; 316 bad |= !get_obj_str(override, "type", override_type, sizeof(override_type)); 317 if (strncmp(override_type, "direct", sizeof(override_type) - 1) == 0) { 318 o->override_type = XRT_TRACKING_OVERRIDE_DIRECT; 319 } else if (strncmp(override_type, "attached", sizeof(override_type) - 1) == 0) { 320 o->override_type = XRT_TRACKING_OVERRIDE_ATTACHED; 321 } 322 323 cJSON *offset = cJSON_GetObjectItemCaseSensitive(override, "offset"); 324 if (offset) { 325 cJSON *orientation = cJSON_GetObjectItemCaseSensitive(offset, "orientation"); 326 bad |= !get_obj_float(orientation, "x", &o->offset.orientation.x); 327 bad |= !get_obj_float(orientation, "y", &o->offset.orientation.y); 328 bad |= !get_obj_float(orientation, "z", &o->offset.orientation.z); 329 bad |= !get_obj_float(orientation, "w", &o->offset.orientation.w); 330 331 cJSON *position = cJSON_GetObjectItemCaseSensitive(offset, "position"); 332 bad |= !get_obj_float(position, "x", &o->offset.position.x); 333 bad |= !get_obj_float(position, "y", &o->offset.position.y); 334 bad |= !get_obj_float(position, "z", &o->offset.position.z); 335 } else { 336 o->offset.orientation.w = 1; 337 } 338 339 char input_name[512] = {'\0'}; 340 get_obj_str(override, "xrt_input_name", input_name, 512); 341 o->input_name = xrt_input_name_enum(input_name); 342 343 if (bad) { 344 *out_override_count = 0; 345 return false; 346 } 347 } 348 return true; 349} 350 351bool 352u_config_json_get_tracking_settings(struct u_config_json *json, struct xrt_settings_tracking *s) 353{ 354 cJSON *t = open_tracking_settings(json); 355 if (t == NULL) { 356 return false; 357 } 358 359 char tmp[16]; 360 361 bool bad = false; 362 363 int ver = -1; 364 365 bad |= !get_obj_int(t, "version", &ver); 366 if (bad || ver >= 1) { 367 U_LOG_E("Missing or unknown version tag '%i' in tracking config", ver); 368 return false; 369 } 370 371 372 bad |= !get_obj_str(t, "camera_name", s->camera_name, sizeof(s->camera_name)); 373 bad |= !get_obj_int(t, "camera_mode", &s->camera_mode); 374 bad |= !get_obj_str(t, "camera_type", tmp, sizeof(tmp)); 375 bad |= !get_obj_str(t, "calibration_path", s->calibration_path, sizeof(s->calibration_path)); 376 if (bad) { 377 return false; 378 } 379 380 if (strcmp(tmp, "regular_mono") == 0) { 381 s->camera_type = XRT_SETTINGS_CAMERA_TYPE_REGULAR_MONO; 382 } else if (strcmp(tmp, "regular_sbs") == 0) { 383 s->camera_type = XRT_SETTINGS_CAMERA_TYPE_REGULAR_SBS; 384 } else if (strcmp(tmp, "ps4") == 0) { 385 s->camera_type = XRT_SETTINGS_CAMERA_TYPE_PS4; 386 } else if (strcmp(tmp, "leap_motion") == 0) { 387 s->camera_type = XRT_SETTINGS_CAMERA_TYPE_LEAP_MOTION; 388 } else { 389 U_LOG_W("Unknown camera type '%s'", tmp); 390 return false; 391 } 392 393 return true; 394} 395 396static void 397u_config_json_make_default_root(struct u_config_json *json) 398{ 399 json->root = cJSON_CreateObject(); 400} 401 402static void 403u_config_write(struct u_config_json *json, const char *filename) 404{ 405 char *str = cJSON_Print(json->root); 406 U_LOG_D("%s", str); 407 408 FILE *config_file = u_file_open_file_in_config_dir(filename, "w"); 409 fprintf(config_file, "%s\n", str); 410 fflush(config_file); 411 fclose(config_file); 412 config_file = NULL; 413 free(str); 414} 415 416void 417u_config_json_save_calibration(struct u_config_json *json, struct xrt_settings_tracking *settings) 418{ 419 if (!json->file_loaded) { 420 u_config_json_make_default_root(json); 421 } 422 u_config_json_assign_schema(json); 423 424 cJSON *root = json->root; 425 426 cJSON *t = cJSON_GetObjectItem(root, "tracking"); 427 if (!t) { 428 t = cJSON_AddObjectToObject(root, "tracking"); 429 } 430 431 cJSON_DeleteItemFromObject(t, "version"); 432 cJSON_AddNumberToObject(t, "version", 0); 433 434 cJSON_DeleteItemFromObject(t, "camera_name"); 435 cJSON_AddStringToObject(t, "camera_name", settings->camera_name); 436 437 cJSON_DeleteItemFromObject(t, "camera_mode"); 438 cJSON_AddNumberToObject(t, "camera_mode", settings->camera_mode); 439 440 cJSON_DeleteItemFromObject(t, "camera_type"); 441 switch (settings->camera_type) { 442 case XRT_SETTINGS_CAMERA_TYPE_REGULAR_MONO: cJSON_AddStringToObject(t, "camera_type", "regular_mono"); break; 443 case XRT_SETTINGS_CAMERA_TYPE_REGULAR_SBS: cJSON_AddStringToObject(t, "camera_type", "regular_sbs"); break; 444 case XRT_SETTINGS_CAMERA_TYPE_SLAM: cJSON_AddStringToObject(t, "camera_type", "slam_sbs"); break; 445 case XRT_SETTINGS_CAMERA_TYPE_PS4: cJSON_AddStringToObject(t, "camera_type", "ps4"); break; 446 case XRT_SETTINGS_CAMERA_TYPE_LEAP_MOTION: cJSON_AddStringToObject(t, "camera_type", "leap_motion"); break; 447 } 448 449 cJSON_DeleteItemFromObject(t, "calibration_path"); 450 cJSON_AddStringToObject(t, "calibration_path", settings->calibration_path); 451 452 u_config_write(json, CONFIG_FILE_NAME); 453} 454 455static cJSON * 456make_pose(struct xrt_pose *pose) 457{ 458 cJSON *json = cJSON_CreateObject(); 459 460 cJSON *o = cJSON_CreateObject(); 461 cJSON_AddNumberToObject(o, "x", pose->orientation.x); 462 cJSON_AddNumberToObject(o, "y", pose->orientation.y); 463 cJSON_AddNumberToObject(o, "z", pose->orientation.z); 464 cJSON_AddNumberToObject(o, "w", pose->orientation.w); 465 cJSON_AddItemToObject(json, "orientation", o); 466 467 cJSON *p = cJSON_CreateObject(); 468 cJSON_AddNumberToObject(p, "x", pose->position.x); 469 cJSON_AddNumberToObject(p, "y", pose->position.y); 470 cJSON_AddNumberToObject(p, "z", pose->position.z); 471 cJSON_AddItemToObject(json, "position", p); 472 473 return json; 474} 475 476void 477u_config_json_save_overrides(struct u_config_json *json, struct xrt_tracking_override *overrides, size_t override_count) 478{ 479 if (!json->file_loaded) { 480 u_config_json_make_default_root(json); 481 } 482 u_config_json_assign_schema(json); 483 cJSON *root = json->root; 484 485 cJSON *t = cJSON_GetObjectItem(root, "tracking"); 486 if (!t) { 487 t = cJSON_AddObjectToObject(root, "tracking"); 488 } 489 490 cJSON_DeleteItemFromObject(t, "tracking_overrides"); 491 cJSON *o = cJSON_AddArrayToObject(t, "tracking_overrides"); 492 493 for (size_t i = 0; i < override_count; i++) { 494 cJSON *entry = cJSON_CreateObject(); 495 496 cJSON_AddStringToObject(entry, "target_device_serial", overrides[i].target_device_serial); 497 cJSON_AddStringToObject(entry, "tracker_device_serial", overrides[i].tracker_device_serial); 498 499 char buffer[256]; 500 switch (overrides[i].override_type) { 501 case XRT_TRACKING_OVERRIDE_DIRECT: snprintf(buffer, ARRAY_SIZE(buffer), "direct"); break; 502 case XRT_TRACKING_OVERRIDE_ATTACHED: snprintf(buffer, ARRAY_SIZE(buffer), "attached"); break; 503 } 504 cJSON_AddStringToObject(entry, "type", buffer); 505 506 cJSON_AddItemToObject(entry, "offset", make_pose(&overrides[i].offset)); 507 508 const char *input_name_string = u_str_xrt_input_name(overrides[i].input_name); 509 cJSON_AddStringToObject(entry, "xrt_input_name", input_name_string); 510 511 cJSON_AddItemToArray(o, entry); 512 } 513 514 u_config_write(json, CONFIG_FILE_NAME); 515} 516 517void 518u_gui_state_open_file(struct u_config_json *json) 519{ 520 u_config_json_open_or_create_file(json, GUI_STATE_FILE_NAME); 521} 522 523static const char * 524u_gui_state_scene_to_string(enum u_gui_state_scene scene) 525{ 526 switch (scene) { 527 case GUI_STATE_SCENE_CALIBRATE: return "calibrate"; 528 default: assert(false); return NULL; 529 } 530} 531 532struct cJSON * 533u_gui_state_get_scene(struct u_config_json *json, enum u_gui_state_scene scene) 534{ 535 if (json->root == NULL) { 536 return NULL; 537 } 538 const char *scene_name = u_gui_state_scene_to_string(scene); 539 540 struct cJSON *c = 541 cJSON_DetachItemFromObjectCaseSensitive(cJSON_GetObjectItemCaseSensitive(json->root, "scenes"), scene_name); 542 cJSON_Delete(json->root); 543 return c; 544} 545 546void 547u_gui_state_save_scene(struct u_config_json *json, enum u_gui_state_scene scene, struct cJSON *new_state) 548{ 549 550 if (!json->file_loaded) { 551 u_config_json_make_default_root(json); 552 } 553 554 cJSON *root = json->root; 555 556 const char *scene_name = u_gui_state_scene_to_string(scene); 557 558 struct cJSON *sc = cJSON_GetObjectItemCaseSensitive(root, "scenes"); 559 560 if (!sc) { 561 sc = cJSON_AddObjectToObject(root, "scenes"); 562 } 563 cJSON_DeleteItemFromObject(sc, scene_name); 564 cJSON_AddItemToObject(sc, scene_name, new_state); 565 u_config_write(json, GUI_STATE_FILE_NAME); 566}