oculus1
view libovr/Src/OVR_Profile.cpp @ 23:0c76f70fb7e9
merged
author | John Tsiombikas <nuclear@member.fsf.org> |
---|---|
date | Sat, 28 Sep 2013 04:13:33 +0300 |
parents | |
children |
line source
1 /************************************************************************************
3 PublicHeader: None
4 Filename : OVR_Profile.cpp
5 Content : Structs and functions for loading and storing device profile settings
6 Created : February 14, 2013
7 Notes :
9 Profiles are used to store per-user settings that can be transferred and used
10 across multiple applications. For example, player IPD can be configured once
11 and reused for a unified experience across games. Configuration and saving of profiles
12 can be accomplished in game via the Profile API or by the official Oculus Configuration
13 Utility.
15 Copyright : Copyright 2013 Oculus VR, Inc. All Rights reserved.
17 Use of this software is subject to the terms of the Oculus license
18 agreement provided at the time of installation or download, or which
19 otherwise accompanies this software in either electronic or hard copy form.
21 ************************************************************************************/
23 #include "OVR_Profile.h"
24 #include "OVR_JSON.h"
25 #include "Kernel/OVR_Types.h"
26 #include "Kernel/OVR_SysFile.h"
27 #include "Kernel/OVR_Allocator.h"
28 #include "Kernel/OVR_Array.h"
30 #ifdef OVR_OS_WIN32
31 #include <Shlobj.h>
32 #else
33 #include <dirent.h>
34 #include <sys/stat.h>
36 #ifdef OVR_OS_LINUX
37 #include <unistd.h>
38 #include <pwd.h>
39 #endif
41 #endif
43 #define PROFILE_VERSION 1.0
45 namespace OVR {
47 //-----------------------------------------------------------------------------
48 // Returns the pathname of the JSON file containing the stored profiles
49 String GetBaseOVRPath(bool create_dir)
50 {
51 String path;
53 #if defined(OVR_OS_WIN32)
55 TCHAR data_path[MAX_PATH];
56 SHGetFolderPath(0, CSIDL_LOCAL_APPDATA, NULL, 0, data_path);
57 path = String(data_path);
59 path += "/Oculus";
61 if (create_dir)
62 { // Create the Oculus directory if it doesn't exist
63 WCHAR wpath[128];
64 OVR::UTF8Util::DecodeString(wpath, path.ToCStr());
66 DWORD attrib = GetFileAttributes(wpath);
67 bool exists = attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY);
68 if (!exists)
69 {
70 CreateDirectory(wpath, NULL);
71 }
72 }
74 #elif defined(OVR_OS_MAC)
76 const char* home = getenv("HOME");
77 path = home;
78 path += "/Library/Preferences/Oculus";
80 if (create_dir)
81 { // Create the Oculus directory if it doesn't exist
82 DIR* dir = opendir(path);
83 if (dir == NULL)
84 {
85 mkdir(path, S_IRWXU | S_IRWXG | S_IRWXO);
86 }
87 else
88 {
89 closedir(dir);
90 }
91 }
93 #else
95 passwd* pwd = getpwuid(getuid());
96 const char* home = pwd->pw_dir;
97 path = home;
98 path += "/.config/Oculus";
100 if (create_dir)
101 { // Create the Oculus directory if it doesn't exist
102 DIR* dir = opendir(path);
103 if (dir == NULL)
104 {
105 mkdir(path, S_IRWXU | S_IRWXG | S_IRWXO);
106 }
107 else
108 {
109 closedir(dir);
110 }
111 }
113 #endif
115 return path;
116 }
118 String GetProfilePath(bool create_dir)
119 {
120 String path = GetBaseOVRPath(create_dir);
121 path += "/Profiles.json";
122 return path;
123 }
125 //-----------------------------------------------------------------------------
126 // ***** ProfileManager
128 ProfileManager::ProfileManager()
129 {
130 Changed = false;
131 CacheDevice = Profile_Unknown;
132 }
134 ProfileManager::~ProfileManager()
135 {
136 // If the profiles have been altered then write out the profile file
137 if (Changed)
138 SaveCache();
140 ClearCache();
141 }
143 ProfileManager* ProfileManager::Create()
144 {
145 return new ProfileManager();
146 }
148 Profile* ProfileManager::CreateProfileObject(const char* user,
149 ProfileType device,
150 const char** device_name)
151 {
152 Lock::Locker lockScope(&ProfileLock);
154 Profile* profile = NULL;
155 switch (device)
156 {
157 case Profile_RiftDK1:
158 *device_name = "RiftDK1";
159 profile = new RiftDK1Profile(user);
160 break;
161 case Profile_RiftDKHD:
162 case Profile_Unknown:
163 break;
164 }
166 return profile;
167 }
170 // Clear the local profile cache
171 void ProfileManager::ClearCache()
172 {
173 Lock::Locker lockScope(&ProfileLock);
175 ProfileCache.Clear();
176 CacheDevice = Profile_Unknown;
177 }
180 // Poplulates the local profile cache. This occurs on the first access of the profile
181 // data. All profile operations are performed against the local cache until the
182 // ProfileManager is released or goes out of scope at which time the cache is serialized
183 // to disk.
184 void ProfileManager::LoadCache(ProfileType device)
185 {
186 Lock::Locker lockScope(&ProfileLock);
188 ClearCache();
190 String path = GetProfilePath(false);
192 Ptr<JSON> root = *JSON::Load(path);
193 if (!root || root->GetItemCount() < 3)
194 return;
196 // First read the file type and version to make sure this is a valid file
197 JSON* item0 = root->GetFirstItem();
198 JSON* item1 = root->GetNextItem(item0);
199 JSON* item2 = root->GetNextItem(item1);
201 if (OVR_strcmp(item0->Name, "Oculus Profile Version") == 0)
202 { // In the future I may need to check versioning to determine parse method
203 }
204 else
205 {
206 return;
207 }
209 DefaultProfile = item1->Value;
211 // Read the number of profiles
212 int profileCount = (int)item2->dValue;
213 JSON* profileItem = item2;
215 for (int p=0; p<profileCount; p++)
216 {
217 profileItem = profileItem->GetNextItem(profileItem);
218 if (!profileItem)
219 break;
221 // Read the required Name field
222 const char* profileName;
223 JSON* item = profileItem->GetFirstItem();
225 if (item && (OVR_strcmp(item->Name, "Name") == 0))
226 {
227 profileName = item->Value;
228 }
229 else
230 {
231 return; // invalid field
232 }
234 const char* deviceName = 0;
235 bool deviceFound = false;
236 Ptr<Profile> profile = *CreateProfileObject(profileName, device, &deviceName);
238 // Read the base profile fields.
239 if (profile)
240 {
241 while (item = profileItem->GetNextItem(item), item)
242 {
243 if (item->Type != JSON_Object)
244 {
245 profile->ParseProperty(item->Name, item->Value);
246 }
247 else
248 { // Search for the matching device to get device specific fields
249 if (!deviceFound && OVR_strcmp(item->Name, deviceName) == 0)
250 {
251 deviceFound = true;
253 for (JSON* deviceItem = item->GetFirstItem(); deviceItem;
254 deviceItem = item->GetNextItem(deviceItem))
255 {
256 profile->ParseProperty(deviceItem->Name, deviceItem->Value);
257 }
258 }
259 }
260 }
261 }
263 // Add the new profile
264 if (deviceFound)
265 ProfileCache.PushBack(profile);
266 }
268 CacheDevice = device;
269 }
272 // Serializes the profiles to disk.
273 void ProfileManager::SaveCache()
274 {
275 String path = GetProfilePath(true);
277 Lock::Locker lockScope(&ProfileLock);
279 // TODO: Since there is only a single device type now, a full tree overwrite
280 // is sufficient but in the future a selective device branch replacement will
281 // be necessary
283 Ptr<JSON> root = *JSON::CreateObject();
284 root->AddNumberItem("Oculus Profile Version", PROFILE_VERSION);
285 root->AddStringItem("CurrentProfile", DefaultProfile);
286 root->AddNumberItem("ProfileCount", (double) ProfileCache.GetSize());
288 // Generate a JSON subtree for each profile
289 for (unsigned int i=0; i<ProfileCache.GetSize(); i++)
290 {
291 Profile* profile = ProfileCache[i];
293 JSON* json_profile = JSON::CreateObject();
294 json_profile->Name = "Profile";
295 json_profile->AddStringItem("Name", profile->Name);
296 const char* gender;
297 switch (profile->GetGender())
298 {
299 case Profile::Gender_Male: gender = "Male"; break;
300 case Profile::Gender_Female: gender = "Female"; break;
301 default: gender = "Unspecified";
302 }
303 json_profile->AddStringItem("Gender", gender);
304 json_profile->AddNumberItem("PlayerHeight", profile->PlayerHeight);
305 json_profile->AddNumberItem("IPD", profile->IPD);
307 if (profile->Type == Profile_RiftDK1)
308 {
309 RiftDK1Profile* riftdk1 = (RiftDK1Profile*)profile;
310 JSON* json_riftdk1 = JSON::CreateObject();
311 json_profile->AddItem("RiftDK1", json_riftdk1);
313 const char* eyecup = "A";
314 switch (riftdk1->EyeCups)
315 {
316 case RiftDK1Profile::EyeCup_A: eyecup = "A"; break;
317 case RiftDK1Profile::EyeCup_B: eyecup = "B"; break;
318 case RiftDK1Profile::EyeCup_C: eyecup = "C"; break;
319 }
320 json_riftdk1->AddStringItem("EyeCup", eyecup);
321 json_riftdk1->AddNumberItem("LL", riftdk1->LL);
322 json_riftdk1->AddNumberItem("LR", riftdk1->LR);
323 json_riftdk1->AddNumberItem("RL", riftdk1->RL);
324 json_riftdk1->AddNumberItem("RR", riftdk1->RR);
325 }
327 root->AddItem("Profile", json_profile);
328 }
330 root->Save(path);
331 }
333 // Returns the number of stored profiles for this device type
334 int ProfileManager::GetProfileCount(ProfileType device)
335 {
336 Lock::Locker lockScope(&ProfileLock);
338 if (CacheDevice == Profile_Unknown)
339 LoadCache(device);
341 return (int)ProfileCache.GetSize();
342 }
344 // Returns the profile name of a specific profile in the list. The returned
345 // memory is locally allocated and should not be stored or deleted. Returns NULL
346 // if the index is invalid
347 const char* ProfileManager::GetProfileName(ProfileType device, unsigned int index)
348 {
349 Lock::Locker lockScope(&ProfileLock);
351 if (CacheDevice == Profile_Unknown)
352 LoadCache(device);
354 if (index < ProfileCache.GetSize())
355 {
356 Profile* profile = ProfileCache[index];
357 OVR_strcpy(NameBuff, Profile::MaxNameLen, profile->Name);
358 return NameBuff;
359 }
360 else
361 {
362 return NULL;
363 }
364 }
366 bool ProfileManager::HasProfile(ProfileType device, const char* name)
367 {
368 Lock::Locker lockScope(&ProfileLock);
370 if (CacheDevice == Profile_Unknown)
371 LoadCache(device);
373 for (unsigned i = 0; i< ProfileCache.GetSize(); i++)
374 {
375 if (ProfileCache[i] && OVR_strcmp(ProfileCache[i]->Name, name) == 0)
376 return true;
377 }
378 return false;
379 }
382 // Returns a specific profile object in the list. The returned memory should be
383 // encapsulated in a Ptr<> object or released after use. Returns NULL if the index
384 // is invalid
385 Profile* ProfileManager::LoadProfile(ProfileType device, unsigned int index)
386 {
387 Lock::Locker lockScope(&ProfileLock);
389 if (CacheDevice == Profile_Unknown)
390 LoadCache(device);
392 if (index < ProfileCache.GetSize())
393 {
394 Profile* profile = ProfileCache[index];
395 return profile->Clone();
396 }
397 else
398 {
399 return NULL;
400 }
401 }
403 // Returns a profile object for a particular device and user name. The returned
404 // memory should be encapsulated in a Ptr<> object or released after use. Returns
405 // NULL if the profile is not found
406 Profile* ProfileManager::LoadProfile(ProfileType device, const char* user)
407 {
408 if (user == NULL)
409 return NULL;
411 Lock::Locker lockScope(&ProfileLock);
413 if (CacheDevice == Profile_Unknown)
414 LoadCache(device);
416 for (unsigned int i=0; i<ProfileCache.GetSize(); i++)
417 {
418 if (OVR_strcmp(user, ProfileCache[i]->Name) == 0)
419 { // Found the requested user profile
420 Profile* profile = ProfileCache[i];
421 return profile->Clone();
422 }
423 }
425 return NULL;
426 }
428 // Returns a profile with all system default values
429 Profile* ProfileManager::GetDeviceDefaultProfile(ProfileType device)
430 {
431 const char* device_name = NULL;
432 return CreateProfileObject("default", device, &device_name);
433 }
435 // Returns the name of the profile that is marked as the current default user.
436 const char* ProfileManager::GetDefaultProfileName(ProfileType device)
437 {
438 Lock::Locker lockScope(&ProfileLock);
440 if (CacheDevice == Profile_Unknown)
441 LoadCache(device);
443 if (ProfileCache.GetSize() > 0)
444 {
445 OVR_strcpy(NameBuff, Profile::MaxNameLen, DefaultProfile);
446 return NameBuff;
447 }
448 else
449 {
450 return NULL;
451 }
452 }
454 // Marks a particular user as the current default user.
455 bool ProfileManager::SetDefaultProfileName(ProfileType device, const char* name)
456 {
457 Lock::Locker lockScope(&ProfileLock);
459 if (CacheDevice == Profile_Unknown)
460 LoadCache(device);
461 // TODO: I should verify that the user is valid
462 if (ProfileCache.GetSize() > 0)
463 {
464 DefaultProfile = name;
465 Changed = true;
466 return true;
467 }
468 else
469 {
470 return false;
471 }
472 }
475 // Saves a new or existing profile. Returns true on success or false on an
476 // invalid or failed save.
477 bool ProfileManager::Save(const Profile* profile)
478 {
479 Lock::Locker lockScope(&ProfileLock);
481 if (OVR_strcmp(profile->Name, "default") == 0)
482 return false; // don't save a default profile
484 // TODO: I should also verify that this profile type matches the current cache
485 if (CacheDevice == Profile_Unknown)
486 LoadCache(profile->Type);
488 // Look for the pre-existence of this profile
489 bool added = false;
490 for (unsigned int i=0; i<ProfileCache.GetSize(); i++)
491 {
492 int compare = OVR_strcmp(profile->Name, ProfileCache[i]->Name);
494 if (compare == 0)
495 {
496 // TODO: I should do a proper field comparison to avoid unnecessary
497 // overwrites and file saves
499 // Replace the previous instance with the new profile
500 ProfileCache[i] = *profile->Clone();
501 added = true;
502 Changed = true;
503 break;
504 }
505 }
507 if (!added)
508 {
509 ProfileCache.PushBack(*profile->Clone());
510 if (ProfileCache.GetSize() == 1)
511 CacheDevice = profile->Type;
513 Changed = true;
514 }
516 return true;
517 }
519 // Removes an existing profile. Returns true if the profile was found and deleted
520 // and returns false otherwise.
521 bool ProfileManager::Delete(const Profile* profile)
522 {
523 Lock::Locker lockScope(&ProfileLock);
525 if (OVR_strcmp(profile->Name, "default") == 0)
526 return false; // don't delete a default profile
528 if (CacheDevice == Profile_Unknown)
529 LoadCache(profile->Type);
531 // Look for the existence of this profile
532 for (unsigned int i=0; i<ProfileCache.GetSize(); i++)
533 {
534 if (OVR_strcmp(profile->Name, ProfileCache[i]->Name) == 0)
535 {
536 if (OVR_strcmp(profile->Name, DefaultProfile) == 0)
537 DefaultProfile.Clear();
539 ProfileCache.RemoveAt(i);
540 Changed = true;
541 return true;
542 }
543 }
545 return false;
546 }
550 //-----------------------------------------------------------------------------
551 // ***** Profile
553 Profile::Profile(ProfileType device, const char* name)
554 {
555 Type = device;
556 Gender = Gender_Unspecified;
557 PlayerHeight = 1.778f; // 5'10" inch man
558 IPD = 0.064f;
559 OVR_strcpy(Name, MaxNameLen, name);
560 }
563 bool Profile::ParseProperty(const char* prop, const char* sval)
564 {
565 if (OVR_strcmp(prop, "Name") == 0)
566 {
567 OVR_strcpy(Name, MaxNameLen, sval);
568 return true;
569 }
570 else if (OVR_strcmp(prop, "Gender") == 0)
571 {
572 if (OVR_strcmp(sval, "Male") == 0)
573 Gender = Gender_Male;
574 else if (OVR_strcmp(sval, "Female") == 0)
575 Gender = Gender_Female;
576 else
577 Gender = Gender_Unspecified;
579 return true;
580 }
581 else if (OVR_strcmp(prop, "PlayerHeight") == 0)
582 {
583 PlayerHeight = (float)atof(sval);
584 return true;
585 }
586 else if (OVR_strcmp(prop, "IPD") == 0)
587 {
588 IPD = (float)atof(sval);
589 return true;
590 }
592 return false;
593 }
596 // Computes the eye height from the metric head height
597 float Profile::GetEyeHeight()
598 {
599 const float EYE_TO_HEADTOP_RATIO = 0.44538f;
600 const float MALE_AVG_HEAD_HEIGHT = 0.232f;
601 const float FEMALE_AVG_HEAD_HEIGHT = 0.218f;
603 // compute distance from top of skull to the eye
604 float head_height;
605 if (Gender == Gender_Female)
606 head_height = FEMALE_AVG_HEAD_HEIGHT;
607 else
608 head_height = MALE_AVG_HEAD_HEIGHT;
610 float skull = EYE_TO_HEADTOP_RATIO * head_height;
612 float eye_height = PlayerHeight - skull;
613 return eye_height;
614 }
617 //-----------------------------------------------------------------------------
618 // ***** RiftDK1Profile
620 RiftDK1Profile::RiftDK1Profile(const char* name) : Profile(Profile_RiftDK1, name)
621 {
622 EyeCups = EyeCup_A;
623 LL = 0;
624 LR = 0;
625 RL = 0;
626 RR = 0;
627 }
629 bool RiftDK1Profile::ParseProperty(const char* prop, const char* sval)
630 {
631 if (OVR_strcmp(prop, "EyeCup") == 0)
632 {
633 switch (sval[0])
634 {
635 case 'C': EyeCups = EyeCup_C; break;
636 case 'B': EyeCups = EyeCup_B; break;
637 default: EyeCups = EyeCup_A; break;
638 }
639 return true;
640 }
641 else if (OVR_strcmp(prop, "LL") == 0)
642 {
643 LL = atoi(sval);
644 return true;
645 }
646 else if (OVR_strcmp(prop, "LR") == 0)
647 {
648 LR = atoi(sval);
649 return true;
650 }
651 else if (OVR_strcmp(prop, "RL") == 0)
652 {
653 RL = atoi(sval);
654 return true;
655 }
656 else if (OVR_strcmp(prop, "RR") == 0)
657 {
658 RR = atoi(sval);
659 return true;
660 }
662 return Profile::ParseProperty(prop, sval);
663 }
665 Profile* RiftDK1Profile::Clone() const
666 {
667 RiftDK1Profile* profile = new RiftDK1Profile(*this);
668 return profile;
669 }
671 } // OVR