The open source OpenXR runtime
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}