summaryrefslogtreecommitdiffstats
path: root/LibOVR/Src/OVR_Profile.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'LibOVR/Src/OVR_Profile.cpp')
-rw-r--r--LibOVR/Src/OVR_Profile.cpp1013
1 files changed, 567 insertions, 446 deletions
diff --git a/LibOVR/Src/OVR_Profile.cpp b/LibOVR/Src/OVR_Profile.cpp
index bf8906e..3be43db 100644
--- a/LibOVR/Src/OVR_Profile.cpp
+++ b/LibOVR/Src/OVR_Profile.cpp
@@ -5,9 +5,9 @@ Filename : OVR_Profile.cpp
Content : Structs and functions for loading and storing device profile settings
Created : February 14, 2013
Notes :
-
+
Profiles are used to store per-user settings that can be transferred and used
- across multiple applications. For example, player IPD can be configured once
+ across multiple applications. For example, player IPD can be configured once
and reused for a unified experience across games. Configuration and saving of profiles
can be accomplished in game via the Profile API or by the official Oculus Configuration
Utility.
@@ -21,13 +21,12 @@ otherwise accompanies this software in either electronic or hard copy form.
************************************************************************************/
#include "OVR_Profile.h"
-#include "OVR_Log.h"
+#include "OVR_JSON.h"
#include "Kernel/OVR_Types.h"
#include "Kernel/OVR_SysFile.h"
#include "Kernel/OVR_Allocator.h"
#include "Kernel/OVR_Array.h"
-#include <json/json.h>
-#include <fstream>
+
#ifdef OVR_OS_WIN32
#include <Shlobj.h>
#else
@@ -41,33 +40,8 @@ otherwise accompanies this software in either electronic or hard copy form.
#endif
-
-#define PROFILE_VERSION 2
-#define MAX_PROFILE_MAJOR_VERSION 2
-
-// Many hard coded strings used in numerous locations have been
-// repositioned here, so that there's no chance of a misspelling
-// causing a problem. Not every string has been moved, but most of the
-// repeated ones have.
-#define KEY_PROFILE_VERSION "Oculus Profile Version"
-#define KEY_CURRENT_PROFILE "CurrentProfile"
-#define KEY_PROFILES "Profiles"
-#define KEY_DEVICES "Devices"
-#define KEY_GENDER "Gender"
-#define KEY_PLAYER_HEIGHT "PlayerHeight"
-#define KEY_IPD "IPD"
-#define KEY_STRABISMUS_CORRECTION "StrabismusCorrection"
-
-#define KEY_LL "LL"
-#define KEY_LR "LR"
-#define KEY_RL "RL"
-#define KEY_RR "RR"
-#define KEY_EYECUP "EyeCup"
-#define EPSILON 0.00001f
-// 5'10" inch man
-#define DEFAULT_HEIGHT 1.778f
-#define DEFAULT_IPD 0.064f
-
+#define PROFILE_VERSION 1.0
+#define MAX_PROFILE_MAJOR_VERSION 1
namespace OVR {
@@ -82,7 +56,7 @@ String GetBaseOVRPath(bool create_dir)
TCHAR data_path[MAX_PATH];
SHGetFolderPath(0, CSIDL_LOCAL_APPDATA, NULL, 0, data_path);
path = String(data_path);
-
+
path += "/Oculus";
if (create_dir)
@@ -93,11 +67,11 @@ String GetBaseOVRPath(bool create_dir)
DWORD attrib = GetFileAttributes(wpath);
bool exists = attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY);
if (!exists)
- {
+ {
CreateDirectory(wpath, NULL);
}
}
-
+
#elif defined(OVR_OS_MAC)
const char* home = getenv("HOME");
@@ -118,21 +92,11 @@ String GetBaseOVRPath(bool create_dir)
}
#else
- // Updated the config folder location logic to rely on the
- // XDG specification for config locations (the XDK location was being
- // used anyway, but was hardcoded). This is analgous to using
- // SHGetFolderPath in the windows implementation, rather than
- // hardcoding %HOME%/AppData/Local
- const char * config_home = getenv("XDG_CONFIG_HOME");
- if (NULL != config_home) {
- path = config_home;
- } else {
- // only if XDG_CONFIG_HOME is unses does the specification say to
- // fallback on the default of $HOME/.config
- path = getenv("HOME");
- path += "/.config";
- }
- path += "/Oculus";
+
+ passwd* pwd = getpwuid(getuid());
+ const char* home = pwd->pw_dir;
+ path = home;
+ path += "/.config/Oculus";
if (create_dir)
{ // Create the Oculus directory if it doesn't exist
@@ -165,7 +129,7 @@ String GetProfilePath(bool create_dir)
ProfileManager::ProfileManager()
{
Changed = false;
- Loaded = false;
+ CacheDevice = Profile_Unknown;
}
ProfileManager::~ProfileManager()
@@ -182,423 +146,410 @@ ProfileManager* ProfileManager::Create()
return new ProfileManager();
}
+Profile* ProfileManager::CreateProfileObject(const char* user,
+ ProfileType device,
+ const char** device_name)
+{
+ Lock::Locker lockScope(&ProfileLock);
+
+ Profile* profile = NULL;
+ switch (device)
+ {
+ case Profile_GenericHMD:
+ *device_name = NULL;
+ profile = new HMDProfile(Profile_GenericHMD, user);
+ break;
+ case Profile_RiftDK1:
+ *device_name = "RiftDK1";
+ profile = new RiftDK1Profile(user);
+ break;
+ case Profile_RiftDKHD:
+ *device_name = "RiftDKHD";
+ profile = new RiftDKHDProfile(user);
+ break;
+ case Profile_Unknown:
+ break;
+ }
+
+ return profile;
+}
+
+
// Clear the local profile cache
void ProfileManager::ClearCache()
{
Lock::Locker lockScope(&ProfileLock);
+
ProfileCache.Clear();
- Loaded = false;
+ CacheDevice = Profile_Unknown;
}
-// Profile loader is an intermediary that allows me to break down the
-// serialization into smaller pieces that work directly against the
-// new native JSON container type. This can't be done in the
-// ProfileManager because it can't declare functions referencing the
-// new type without polluting the header with knowledge of the JSON
-// implementation
-class ProfileLoader {
-public:
- static void loadHmd(HmdDevice & device, const Json::Value & node) {
- if (node.isNull()) {
- return;
- }
- if (node.isMember(KEY_LL)) {
- device.LL = node.get(KEY_LL, 0).asInt();
- }
- if (node.isMember(KEY_LR)) {
- device.LR = node.get(KEY_LR, 0).asInt();
- }
- if (node.isMember(KEY_RL)) {
- device.RL = node.get(KEY_RL, 0).asInt();
- }
- if (node.isMember(KEY_RR)) {
- device.RR = node.get(KEY_RR, 0).asInt();
- }
- }
-
- static void loadRift(RiftDevice & device, const Json::Value & node) {
- if (node.isNull()) {
- return;
- }
- String eyeCups = node.get(KEY_EYECUP, "A").asCString();
- char c = eyeCups.GetSize() ? eyeCups.GetCharAt(0) : 'A';
- switch (eyeCups.GetCharAt(0)) {
- case 'A':
- device.EyeCups = EyeCupType::EyeCup_A;
- break;
- case 'B':
- device.EyeCups = EyeCupType::EyeCup_B;
- break;
- case 'C':
- device.EyeCups = EyeCupType::EyeCup_C;
- break;
- }
- loadHmd(device, node);
- }
-
- // TODO migrate all the string constants up
- static void loadV1Profile(Profile & out, const Json::Value & node) {
- if (node.isNull()) {
- return;
- }
- String gender = node.get(KEY_GENDER, "Unknown").asCString();
- if (gender == "Male") {
- out.Gender = Profile::GenderType::Gender_Male;
- } else if (gender == "Female") {
- out.Gender = Profile::GenderType::Gender_Female;
- } else {
- out.Gender = Profile::GenderType::Gender_Unspecified;
- }
-
- out.PlayerHeight = node.get(KEY_PLAYER_HEIGHT, DEFAULT_HEIGHT).asFloat();
- out.IPD = node.get(KEY_IPD, DEFAULT_IPD).asFloat();
- loadHmd(out.Generic, node.get("GenericHMD", Json::Value::null));
- loadRift(out.RiftDK1, node.get("RiftDK1", Json::Value::null));
- loadRift(out.RiftDKHD, node.get("RiftDKHD", Json::Value::null));
- }
-
- static void loadProfile(Profile & out, const Json::Value & node) {
- if (node.isNull()) {
- return;
- }
- loadV1Profile(out, node);
- if (node.isMember(KEY_STRABISMUS_CORRECTION)) {
- loadQuaternion(out.StrabismusCorrection, node[KEY_STRABISMUS_CORRECTION]);
- }
- }
-
- static void loadQuaternion(Quatf & out, const Json::Value & node) {
- if (node.isNull()) {
- return;
- }
- out.x = node.get("X", 0).asInt();
- out.y = node.get("Y", 0).asInt();
- out.z = node.get("Z", 0).asInt();
- out.w = node.get("W", 1).asInt();
- }
-
- static void saveQuaternion(Json::Value & out, const Quatf & q) {
- out["X"] = q.x;
- out["Y"] = q.y;
- out["Z"] = q.z;
- out["W"] = q.w;
- }
-
- // TODO implement, and migrate the Devices.json parsing code to use this
- static void loadMatrix(Matrix4f & out, const Json::Value & node) {
- }
-
- //
- // The general pattern on writing Json is to write the node only if
- // it differs from the default, and to explicitly remove the node if it
- // is the same as the default. This is important, because the load / save
- // mechanism is designed to preserve any pre-existing content and not
- // to touch any fields it's not aware of.
- //
- // Therefore, failing to write out a default value doesn't mean there's
- // not already a non-default value there, hence the explicit removes.
- // It gets a little verbose but the result is greater extensibility of the
- // profile data by third parties.
- static void writeHmdDevice(Json::Value & out, const HmdDevice & device) {
- if (device.LL != 0) {
- out[KEY_LL] = device.LL;
- } else {
- out.removeMember(KEY_LL);
- }
- if (device.LR != 0) {
- out[KEY_LR] = device.LR;
- } else {
- out.removeMember(KEY_LR);
- }
- if (device.RL != 0) {
- out[KEY_RL] = device.RL;
- } else {
- out.removeMember(KEY_RL);
- }
- if (device.RR != 0) {
- out[KEY_RR] = device.RR;
- } else {
- out.removeMember(KEY_RR);
- }
- }
-
- static void writeRiftDevice(Json::Value & deviceNode, const RiftDevice & device) {
- switch (device.EyeCups)
- {
- case EyeCup_B: deviceNode[KEY_EYECUP] = "B"; break;
- case EyeCup_C: deviceNode[KEY_EYECUP] = "C"; break;
- // A is the default, so no need to serialize it
- case EyeCup_A: deviceNode.removeMember(KEY_EYECUP); break;
- }
- writeHmdDevice(deviceNode, device);
- }
-
- // Applying the "remove nodes if they're empty" logic here is a little
- // arduous, but it makes the resulting JSON cleaner
- static void updateDeviceProfile(Json::Value & parent, const HmdDevice & device) {
- String name;
- Json::Value newChild;
- switch (device.GetDeviceType()) {
- case Profile_RiftDK1:
- name = "RiftDK1";
- newChild = parent[name];
- writeRiftDevice(newChild, (RiftDevice&)device);
- break;
-
- case Profile_RiftDKHD:
- name = "RiftDKHD";
- newChild = parent[name];
- writeRiftDevice(newChild, (RiftDevice&)device);
- break;
-
- case Profile_GenericHMD:
- name = "GenericHMD";
- newChild = parent[name];
- writeHmdDevice(newChild, device);
- break;
- }
-
- // Don't write empty children
- if (newChild.getMemberNames().size()) {
- parent[name] = newChild;
- } else {
- parent.removeMember(name);
- }
- }
-
- static void writeProfile(Json::Value & parent, const Profile & profile) {
- Json::Value & out = parent[profile.Name];
-
- switch (profile.GetGender()) {
- case Profile::Gender_Male:
- out[KEY_GENDER] = "Male";
- break;
- case Profile::Gender_Female:
- out[KEY_GENDER] = "Female";
- break;
- default:
- out.removeMember(KEY_GENDER);
- }
-
- // Epsilon is 10 micrometers. Smaller than a human hair
- if (abs(profile.PlayerHeight - DEFAULT_HEIGHT) > EPSILON) {
- out[KEY_PLAYER_HEIGHT] = profile.PlayerHeight;
- } else {
- out.removeMember(KEY_PLAYER_HEIGHT);
- }
-
- if (abs(profile.IPD - DEFAULT_IPD) > EPSILON) {
- out[KEY_IPD] = profile.IPD;
- } else {
- out.removeMember(KEY_IPD);
- }
-
- if (profile.StrabismusCorrection.Distance(Quatf()) > EPSILON) {
- saveQuaternion(out[KEY_STRABISMUS_CORRECTION],
- profile.StrabismusCorrection);
- } else {
- out.removeMember(KEY_STRABISMUS_CORRECTION);
- }
-
- Json::Value & devicesNode = out[KEY_DEVICES];
- updateDeviceProfile(devicesNode, profile.Generic);
- updateDeviceProfile(devicesNode, profile.RiftDK1);
- updateDeviceProfile(devicesNode, profile.RiftDKHD);
- if (!devicesNode.getMemberNames().size()) {
- out.removeMember(KEY_DEVICES);
- }
- }
-};
-
-// Use of STL IO is potentially avoidable with jsoncpp, if you wanted
-// to load the file into a String instead and use the char* interface for
-// parsing JSON in memory. Linking to C++ standard library is unavoidable
-// though, but it might not take much work to remove iostream usage from
-// the jsoncpp lib and make it use std::string only.
-bool parseJsonFile(const String & file, Json::Value & root) {
- Json::Reader reader;
- String path = GetProfilePath(false);
- std::ifstream in(path.ToCStr());
- if (!reader.parse(in, root)) {
- // report to the user the failure and their locations in the document.
- LogError("Failed to parse json file: %s\n %s",
- file.ToCStr(),
- reader.getFormattedErrorMessages().c_str());
- return false;
- }
- return true;
-
-}
// Poplulates the local profile cache. This occurs on the first access of the profile
// data. All profile operations are performed against the local cache until the
// ProfileManager is released or goes out of scope at which time the cache is serialized
// to disk.
-void ProfileManager::LoadCache() {
- if (Loaded) {
- return;
- }
-
- Lock::Locker lockScope(&ProfileLock);
- ClearCache();
-
- Json::Value root;
- if (!parseJsonFile(GetProfilePath(false), root)) {
- LogError("Failed to parse configuration");
- return;
- }
-
- if (!root.isMember(KEY_PROFILE_VERSION)) {
- LogError("Profile JSON is malformed, missing version number");
- return;
- }
-
- int major = root[KEY_PROFILE_VERSION].asInt();
- switch (major) {
- case 1: {
- if (root.size() < 3) {
- LogError("Profile JSON is malformed, insufficient keys");
+void ProfileManager::LoadCache(ProfileType device)
+{
+ Lock::Locker lockScope(&ProfileLock);
+
+ ClearCache();
+
+ String path = GetProfilePath(false);
+
+ Ptr<JSON> root = *JSON::Load(path);
+ if (!root || root->GetItemCount() < 3)
return;
- }
- DefaultProfile = root[KEY_CURRENT_PROFILE].asCString();
- const Json::Value & profileNode = root["Profile"];
- String name = profileNode["Name"].asCString();
- // The profile having to know it's own name is a symptom of the lack
- // of a proper associative array class in the OVR codebase.
- Profile * profile = new Profile(name);
- ProfileLoader::loadV1Profile(*profile, profileNode);
- ProfileCache.PushBack(profile);
- }
- break;
-
- case 2: {
- if (!root.isMember(KEY_PROFILES)) {
- LogError("Missing profile data");
+
+ // First read the file type and version to make sure this is a valid file
+ JSON* item0 = root->GetFirstItem();
+ JSON* item1 = root->GetNextItem(item0);
+ JSON* item2 = root->GetNextItem(item1);
+
+ if (item0->Name == "Oculus Profile Version")
+ {
+ int major = atoi(item0->Value.ToCStr());
+ if (major > MAX_PROFILE_MAJOR_VERSION)
+ return; // don't parse the file on unsupported major version number
+ }
+ else
+ {
return;
- }
-
- const Json::Value & profiles = root[KEY_PROFILES];
- Json::Value::const_iterator itr;
- for (itr = profiles.begin(); itr != profiles.end(); ++itr) {
- String profileName = itr.memberName();
- Profile * profile = new Profile(profileName);
- ProfileLoader::loadProfile(*profile, root[profileName]);
- ProfileCache.PushBack(profile);
- }
-
- if (!root.isMember(KEY_CURRENT_PROFILE)) {
- LogError("Missing current profile");
- } else {
- DefaultProfile = root[KEY_CURRENT_PROFILE].asCString();
- }
- }
- break;
-
- default:
- LogError("Usupported profile version %d", major);
- return; // don't parse the file on unsupported major version number
- }
- Loaded = true;
+ }
+
+ DefaultProfile = item1->Value;
+
+ // Read the number of profiles
+ int profileCount = (int)item2->dValue;
+ JSON* profileItem = item2;
+
+ for (int p=0; p<profileCount; p++)
+ {
+ profileItem = root->GetNextItem(profileItem);
+ if (profileItem == NULL)
+ break;
+
+ if (profileItem->Name == "Profile")
+ {
+ // Read the required Name field
+ const char* profileName;
+ JSON* item = profileItem->GetFirstItem();
+
+ if (item && (item->Name == "Name"))
+ {
+ profileName = item->Value;
+ }
+ else
+ {
+ return; // invalid field
+ }
+
+ const char* deviceName = 0;
+ bool deviceFound = false;
+ Ptr<Profile> profile = *CreateProfileObject(profileName, device, &deviceName);
+
+ // Read the base profile fields.
+ if (profile)
+ {
+ while (item = profileItem->GetNextItem(item), item)
+ {
+ if (item->Type != JSON_Object)
+ {
+ profile->ParseProperty(item->Name, item->Value);
+ }
+ else
+ { // Search for the matching device to get device specific fields
+ if (!deviceFound && deviceName && OVR_strcmp(item->Name, deviceName) == 0)
+ {
+ deviceFound = true;
+
+ for (JSON* deviceItem = item->GetFirstItem(); deviceItem;
+ deviceItem = item->GetNextItem(deviceItem))
+ {
+ profile->ParseProperty(deviceItem->Name, deviceItem->Value);
+ }
+ }
+ }
+ }
+ }
+
+ // Add the new profile
+ ProfileCache.PushBack(profile);
+ }
+ }
+
+ CacheDevice = device;
}
+
// Serializes the profiles to disk.
void ProfileManager::SaveCache()
{
+ String path = GetProfilePath(true);
+
Lock::Locker lockScope(&ProfileLock);
- // Limit scope of reader
- Json::Value root;
- String path = GetProfilePath(false);
- if (!parseJsonFile(path, root))
+
+ Ptr<JSON> oldroot = *JSON::Load(path);
+ if (oldroot)
{
- root = Json::Value();
+ if (oldroot->GetItemCount() >= 3)
+ {
+ JSON* item0 = oldroot->GetFirstItem();
+ JSON* item1 = oldroot->GetNextItem(item0);
+ oldroot->GetNextItem(item1);
+
+ if (item0->Name == "Oculus Profile Version")
+ {
+ int major = atoi(item0->Value.ToCStr());
+ if (major > MAX_PROFILE_MAJOR_VERSION)
+ oldroot.Clear(); // don't use the file on unsupported major version number
+ }
+ else
+ {
+ oldroot.Clear();
+ }
+ }
+ else
+ {
+ oldroot.Clear();
+ }
}
+
+ // Create a new json root
+ Ptr<JSON> root = *JSON::CreateObject();
+ root->AddNumberItem("Oculus Profile Version", PROFILE_VERSION);
+ root->AddStringItem("CurrentProfile", DefaultProfile);
+ root->AddNumberItem("ProfileCount", (double) ProfileCache.GetSize());
- // If the file is V1, we don't want to preserve it.
- // Technically, we could make an effort to preserve it but explcitly
- // remove the V1 tokens we don't use in V2, but that's a bunch of
- // effort that will likely serve no useful purpose. Since the file
- // isn't valid JSON if it has multiple profiles, I doubt anyone is
- // extending it yet
- if (root.isMember(KEY_PROFILE_VERSION) &&
- PROFILE_VERSION != root[KEY_PROFILE_VERSION].asInt()) {
- root = Json::Value();
- }
+ // Generate a JSON subtree for each profile
+ for (unsigned int i=0; i<ProfileCache.GetSize(); i++)
+ {
+ Profile* profile = ProfileCache[i];
+
+ // Write the base profile information
+ JSON* json_profile = JSON::CreateObject();
+ json_profile->Name = "Profile";
+ json_profile->AddStringItem("Name", profile->Name);
+ const char* gender;
+ switch (profile->GetGender())
+ {
+ case Profile::Gender_Male: gender = "Male"; break;
+ case Profile::Gender_Female: gender = "Female"; break;
+ default: gender = "Unspecified";
+ }
+ json_profile->AddStringItem("Gender", gender);
+ json_profile->AddNumberItem("PlayerHeight", profile->PlayerHeight);
+ json_profile->AddNumberItem("IPD", profile->IPD);
- root[KEY_PROFILE_VERSION] = PROFILE_VERSION;
- if (!DefaultProfile.IsEmpty()) {
- root[KEY_CURRENT_PROFILE] = DefaultProfile.ToCStr();
- }
+ char* device_name = NULL;
+ // Create a device-specific subtree for the cached device
+ if (profile->Type == Profile_RiftDK1)
+ {
+ device_name = "RiftDK1";
+
+ RiftDK1Profile* rift = (RiftDK1Profile*)profile;
+ JSON* json_rift = JSON::CreateObject();
+ json_profile->AddItem(device_name, json_rift);
+
+ const char* eyecup = "A";
+ switch (rift->EyeCups)
+ {
+ case EyeCup_A: eyecup = "A"; break;
+ case EyeCup_B: eyecup = "B"; break;
+ case EyeCup_C: eyecup = "C"; break;
+ }
+ json_rift->AddStringItem("EyeCup", eyecup);
+ json_rift->AddNumberItem("LL", rift->LL);
+ json_rift->AddNumberItem("LR", rift->LR);
+ json_rift->AddNumberItem("RL", rift->RL);
+ json_rift->AddNumberItem("RR", rift->RR);
+ }
+ else if (profile->Type == Profile_RiftDKHD)
+ {
+ device_name = "RiftDKHD";
+
+ RiftDKHDProfile* rift = (RiftDKHDProfile*)profile;
+ JSON* json_rift = JSON::CreateObject();
+ json_profile->AddItem(device_name, json_rift);
+
+ const char* eyecup = "A";
+ switch (rift->EyeCups)
+ {
+ case EyeCup_A: eyecup = "A"; break;
+ case EyeCup_B: eyecup = "B"; break;
+ case EyeCup_C: eyecup = "C"; break;
+ }
+ json_rift->AddStringItem("EyeCup", eyecup);
+ //json_rift->AddNumberItem("LL", rift->LL);
+ //json_rift->AddNumberItem("LR", rift->LR);
+ //json_rift->AddNumberItem("RL", rift->RL);
+ //json_rift->AddNumberItem("RR", rift->RR);
+ }
+
+ // There may be multiple devices stored per user, but only a single
+ // device is represented by this root. We don't want to overwrite
+ // the other devices so we need to examine the older root
+ // and merge previous devices into new json root
+ if (oldroot)
+ {
+ JSON* old_profile = oldroot->GetFirstItem();
+ while (old_profile)
+ {
+ if (old_profile->Name == "Profile")
+ {
+ JSON* profile_name = old_profile->GetItemByName("Name");
+ if (profile_name && OVR_strcmp(profile->Name, profile_name->Value) == 0)
+ { // Now that we found the user in the older root, add all the
+ // object children to the new root - except for the one for the
+ // current device
+ JSON* old_item = old_profile->GetFirstItem();
+ while (old_item)
+ {
+ if (old_item->Type == JSON_Object
+ && (device_name == NULL || OVR_strcmp(old_item->Name, device_name) != 0))
+ {
+ JSON* old_device = old_item;
+ old_item = old_profile->GetNextItem(old_item);
+
+ // remove the node from the older root to avoid multiple reference
+ old_device->RemoveNode();
+ // add the node pointer to the new root
+ json_profile->AddItem(old_device->Name, old_device);
+ }
+ else
+ {
+ old_item = old_profile->GetNextItem(old_item);
+ }
+ }
+
+ break;
+ }
+ }
+
+ old_profile = oldroot->GetNextItem(old_profile);
+ }
+ }
- // Generate a JSON object of 'profile name' to 'profile data'
- Json::Value & profiles = root[KEY_PROFILES];
- for (unsigned int i=0; i<ProfileCache.GetSize(); i++) {
- const Profile * profile = ProfileCache[i];
- ProfileLoader::writeProfile(profiles, *profile);
+ // Add the completed user profile to the new root
+ root->AddItem("Profile", json_profile);
}
+ // Save the profile to disk
+ root->Save(path);
+}
+
+// Returns the number of stored profiles for this device type
+int ProfileManager::GetProfileCount(ProfileType device)
+{
+ Lock::Locker lockScope(&ProfileLock);
+
+ if (CacheDevice == Profile_Unknown)
+ LoadCache(device);
+
+ return (int)ProfileCache.GetSize();
+}
+
+// Returns the profile name of a specific profile in the list. The returned
+// memory is locally allocated and should not be stored or deleted. Returns NULL
+// if the index is invalid
+const char* ProfileManager::GetProfileName(ProfileType device, unsigned int index)
+{
+ Lock::Locker lockScope(&ProfileLock);
+
+ if (CacheDevice == Profile_Unknown)
+ LoadCache(device);
+
+ if (index < ProfileCache.GetSize())
{
- Json::StyledWriter writer;
- std::string output = writer.write( root );
- SysFile f;
- if (!f.Open(path, File::Open_Write | File::Open_Create | File::Open_Truncate, File::Mode_Write)) {
- LogError("Unable to open %s for writing", path.ToCStr());
- return;
- }
- int written = f.Write((const unsigned char*)output.c_str(),
- output.length());
- if (written != output.length()) {
- LogError("Short write, only %d of %d bytes written",
- written, (int)output.length());
- }
- f.Close();
+ Profile* profile = ProfileCache[index];
+ OVR_strcpy(NameBuff, Profile::MaxNameLen, profile->Name);
+ return NameBuff;
+ }
+ else
+ {
+ return NULL;
}
}
-// Returns the number of stored profiles for this device type
-unsigned int ProfileManager::GetProfileCount()
+bool ProfileManager::HasProfile(ProfileType device, const char* name)
{
Lock::Locker lockScope(&ProfileLock);
- return ProfileCache.GetSize();
+
+ if (CacheDevice == Profile_Unknown)
+ LoadCache(device);
+
+ for (unsigned i = 0; i< ProfileCache.GetSize(); i++)
+ {
+ if (ProfileCache[i] && OVR_strcmp(ProfileCache[i]->Name, name) == 0)
+ return true;
+ }
+ return false;
}
-bool ProfileManager::HasProfile(const char* name)
+
+// Returns a specific profile object in the list. The returned memory should be
+// encapsulated in a Ptr<> object or released after use. Returns NULL if the index
+// is invalid
+Profile* ProfileManager::LoadProfile(ProfileType device, unsigned int index)
{
- return NULL != ProfileCache.at(name);
+ Lock::Locker lockScope(&ProfileLock);
+
+ if (CacheDevice == Profile_Unknown)
+ LoadCache(device);
+
+ if (index < ProfileCache.GetSize())
+ {
+ Profile* profile = ProfileCache[index];
+ return profile->Clone();
+ }
+ else
+ {
+ return NULL;
+ }
}
-// Returns a profile object for a particular device and user name. The returned
-// memory should be encapsulated in a Ptr<> object or released after use. Returns
+// Returns a profile object for a particular device and user name. The returned
+// memory should be encapsulated in a Ptr<> object or released after use. Returns
// NULL if the profile is not found
-Profile* ProfileManager::LoadProfile(const char* user)
+Profile* ProfileManager::LoadProfile(ProfileType device, const char* user)
{
- // Maybe 'null' should be interpreted as 'return the default?'
if (user == NULL)
return NULL;
Lock::Locker lockScope(&ProfileLock);
- LoadCache();
- Profile * result = ProfileCache.at(user);
- if (!result) {
- return NULL;
+
+ if (CacheDevice == Profile_Unknown)
+ LoadCache(device);
+
+ for (unsigned int i=0; i<ProfileCache.GetSize(); i++)
+ {
+ if (OVR_strcmp(user, ProfileCache[i]->Name) == 0)
+ { // Found the requested user profile
+ Profile* profile = ProfileCache[i];
+ return profile->Clone();
+ }
}
- // Never give the caller memory that we ourselves are managing.
- return result->Clone();
+
+ return NULL;
}
// Returns a profile with all system default values
-Profile* ProfileManager::GetDefaultProfile()
+Profile* ProfileManager::GetDeviceDefaultProfile(ProfileType device)
{
- return new Profile("default");
+ const char* device_name = NULL;
+ return CreateProfileObject("default", device, &device_name);
}
// Returns the name of the profile that is marked as the current default user.
-const char* ProfileManager::GetDefaultProfileName()
+const char* ProfileManager::GetDefaultProfileName(ProfileType device)
{
Lock::Locker lockScope(&ProfileLock);
- LoadCache();
+
+ if (CacheDevice == Profile_Unknown)
+ LoadCache(device);
if (ProfileCache.GetSize() > 0)
{
- OVR_strcpy(NameBuff, 32, DefaultProfile);
+ OVR_strcpy(NameBuff, Profile::MaxNameLen, DefaultProfile);
return NameBuff;
}
else
@@ -607,12 +558,14 @@ const char* ProfileManager::GetDefaultProfileName()
}
}
-
// Marks a particular user as the current default user.
-bool ProfileManager::SetDefaultProfileName(const char* name) {
+bool ProfileManager::SetDefaultProfileName(ProfileType device, const char* name)
+{
Lock::Locker lockScope(&ProfileLock);
- LoadCache();
- // TODO: I should verify that the user is valid
+
+ if (CacheDevice == Profile_Unknown)
+ LoadCache(device);
+// TODO: I should verify that the user is valid
if (ProfileCache.GetSize() > 0)
{
DefaultProfile = name;
@@ -628,67 +581,132 @@ bool ProfileManager::SetDefaultProfileName(const char* name) {
// Saves a new or existing profile. Returns true on success or false on an
// invalid or failed save.
-bool ProfileManager::Save(const Profile * profile)
+bool ProfileManager::Save(const Profile* profile)
{
- if (NULL == profile) {
- return false;
- }
+ Lock::Locker lockScope(&ProfileLock);
if (OVR_strcmp(profile->Name, "default") == 0)
return false; // don't save a default profile
- Lock::Locker lockScope(&ProfileLock);
- LoadCache();
+ // TODO: I should also verify that this profile type matches the current cache
+ if (CacheDevice == Profile_Unknown)
+ LoadCache(profile->Type);
// Look for the pre-existence of this profile
- int index = ProfileCache.IndexOf(profile->Name);
- if (Map::npos == index) {
- // TODO: I should do a proper field comparison to avoid unnecessary
- // overwrites and file saves
+ bool added = false;
+ for (unsigned int i=0; i<ProfileCache.GetSize(); i++)
+ {
+ int compare = OVR_strcmp(profile->Name, ProfileCache[i]->Name);
+
+ if (compare == 0)
+ {
+ // TODO: I should do a proper field comparison to avoid unnecessary
+ // overwrites and file saves
+
+ // Replace the previous instance with the new profile
+ ProfileCache[i] = *profile->Clone();
+ added = true;
+ Changed = true;
+ break;
+ }
+ }
+
+ if (!added)
+ {
+ ProfileCache.PushBack(*profile->Clone());
+ if (ProfileCache.GetSize() == 1)
+ CacheDevice = profile->Type;
- // Replace the previous instance with the new profile
- ProfileCache[index] = profile->Clone();
- } else {
- ProfileCache.PushBack(profile->Clone());
+ Changed = true;
}
- Changed = true;
+
return true;
}
// Removes an existing profile. Returns true if the profile was found and deleted
// and returns false otherwise.
-bool ProfileManager::Delete(const Profile * profile)
+bool ProfileManager::Delete(const Profile* profile)
{
- if (NULL == profile) {
- return false;
- }
Lock::Locker lockScope(&ProfileLock);
- int index = ProfileCache.IndexOf(profile->Name);
- if (Map::npos == index) {
- return false;
+
+ if (OVR_strcmp(profile->Name, "default") == 0)
+ return false; // don't delete a default profile
+
+ if (CacheDevice == Profile_Unknown)
+ LoadCache(profile->Type);
+
+ // Look for the existence of this profile
+ for (unsigned int i=0; i<ProfileCache.GetSize(); i++)
+ {
+ if (OVR_strcmp(profile->Name, ProfileCache[i]->Name) == 0)
+ {
+ if (OVR_strcmp(profile->Name, DefaultProfile) == 0)
+ DefaultProfile.Clear();
+
+ ProfileCache.RemoveAt(i);
+ Changed = true;
+ return true;
+ }
}
- ProfileCache.RemoveAt(index);
- return true;
+
+ return false;
}
+
+
//-----------------------------------------------------------------------------
// ***** Profile
-Profile::Profile(const char* name)
+Profile::Profile(ProfileType device, const char* name)
{
+ Type = device;
Gender = Gender_Unspecified;
- PlayerHeight = DEFAULT_HEIGHT;
- IPD = DEFAULT_IPD;
- Name = name;
+ PlayerHeight = 1.778f; // 5'10" inch man
+ IPD = 0.064f;
+ OVR_strcpy(Name, MaxNameLen, name);
+}
+
+
+bool Profile::ParseProperty(const char* prop, const char* sval)
+{
+ if (OVR_strcmp(prop, "Name") == 0)
+ {
+ OVR_strcpy(Name, MaxNameLen, sval);
+ return true;
+ }
+ else if (OVR_strcmp(prop, "Gender") == 0)
+ {
+ if (OVR_strcmp(sval, "Male") == 0)
+ Gender = Gender_Male;
+ else if (OVR_strcmp(sval, "Female") == 0)
+ Gender = Gender_Female;
+ else
+ Gender = Gender_Unspecified;
+
+ return true;
+ }
+ else if (OVR_strcmp(prop, "PlayerHeight") == 0)
+ {
+ PlayerHeight = (float)atof(sval);
+ return true;
+ }
+ else if (OVR_strcmp(prop, "IPD") == 0)
+ {
+ IPD = (float)atof(sval);
+ return true;
+ }
+
+ return false;
}
+
// Computes the eye height from the metric head height
-float Profile::GetEyeHeight() const
+float Profile::GetEyeHeight()
{
const float EYE_TO_HEADTOP_RATIO = 0.44538f;
const float MALE_AVG_HEAD_HEIGHT = 0.232f;
const float FEMALE_AVG_HEAD_HEIGHT = 0.218f;
-
+
// compute distance from top of skull to the eye
float head_height;
if (Gender == Gender_Female)
@@ -702,4 +720,107 @@ float Profile::GetEyeHeight() const
return eye_height;
}
+//-----------------------------------------------------------------------------
+// ***** HMDProfile
+
+HMDProfile::HMDProfile(ProfileType type, const char* name) : Profile(type, name)
+{
+ LL = 0;
+ LR = 0;
+ RL = 0;
+ RR = 0;
+}
+
+bool HMDProfile::ParseProperty(const char* prop, const char* sval)
+{
+ if (OVR_strcmp(prop, "LL") == 0)
+ {
+ LL = atoi(sval);
+ return true;
+ }
+ else if (OVR_strcmp(prop, "LR") == 0)
+ {
+ LR = atoi(sval);
+ return true;
+ }
+ else if (OVR_strcmp(prop, "RL") == 0)
+ {
+ RL = atoi(sval);
+ return true;
+ }
+ else if (OVR_strcmp(prop, "RR") == 0)
+ {
+ RR = atoi(sval);
+ return true;
+ }
+
+ return Profile::ParseProperty(prop, sval);
+}
+
+Profile* HMDProfile::Clone() const
+{
+ HMDProfile* profile = new HMDProfile(*this);
+ return profile;
+}
+
+//-----------------------------------------------------------------------------
+// ***** RiftDK1Profile
+
+RiftDK1Profile::RiftDK1Profile(const char* name) : HMDProfile(Profile_RiftDK1, name)
+{
+ EyeCups = EyeCup_A;
+}
+
+bool RiftDK1Profile::ParseProperty(const char* prop, const char* sval)
+{
+ if (OVR_strcmp(prop, "EyeCup") == 0)
+ {
+ switch (sval[0])
+ {
+ case 'C': EyeCups = EyeCup_C; break;
+ case 'B': EyeCups = EyeCup_B; break;
+ default: EyeCups = EyeCup_A; break;
+ }
+ return true;
+ }
+
+ return HMDProfile::ParseProperty(prop, sval);
+}
+
+Profile* RiftDK1Profile::Clone() const
+{
+ RiftDK1Profile* profile = new RiftDK1Profile(*this);
+ return profile;
+}
+
+//-----------------------------------------------------------------------------
+// ***** RiftDKHDProfile
+
+RiftDKHDProfile::RiftDKHDProfile(const char* name) : HMDProfile(Profile_RiftDKHD, name)
+{
+ EyeCups = EyeCup_A;
+}
+
+bool RiftDKHDProfile::ParseProperty(const char* prop, const char* sval)
+{
+ if (OVR_strcmp(prop, "EyeCup") == 0)
+ {
+ switch (sval[0])
+ {
+ case 'C': EyeCups = EyeCup_C; break;
+ case 'B': EyeCups = EyeCup_B; break;
+ default: EyeCups = EyeCup_A; break;
+ }
+ return true;
+ }
+
+ return HMDProfile::ParseProperty(prop, sval);
+}
+
+Profile* RiftDKHDProfile::Clone() const
+{
+ RiftDKHDProfile* profile = new RiftDKHDProfile(*this);
+ return profile;
+}
+
} // OVR