mirror of
https://gitlab.com/gabmus/envision.git
synced 2025-04-19 19:14:53 +00:00
412 lines
12 KiB
Rust
412 lines
12 KiB
Rust
use crate::{
|
|
file_utils::get_writer,
|
|
paths::{
|
|
data_monado_path, data_opencomposite_path, get_data_dir, get_ipc_file_path,
|
|
BWRAP_SYSTEM_PREFIX, SYSTEM_PREFIX,
|
|
},
|
|
};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::{collections::HashMap, fmt::Display, fs::File, io::BufReader, path::Path, slice::Iter};
|
|
use uuid::Uuid;
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub enum XRServiceType {
|
|
Monado,
|
|
Wivrn,
|
|
}
|
|
|
|
impl XRServiceType {
|
|
pub fn from_string(s: String) -> Self {
|
|
match s.trim().to_lowercase().as_str() {
|
|
"monado" => Self::Monado,
|
|
"wivrn" => Self::Wivrn,
|
|
_ => Self::Monado,
|
|
}
|
|
}
|
|
|
|
pub fn iter() -> Iter<'static, Self> {
|
|
[Self::Monado, Self::Wivrn].iter()
|
|
}
|
|
|
|
pub fn as_number(&self) -> u32 {
|
|
match self {
|
|
Self::Monado => 0,
|
|
Self::Wivrn => 1,
|
|
}
|
|
}
|
|
|
|
pub fn from_number(i: u32) -> Self {
|
|
match i {
|
|
0 => Self::Monado,
|
|
1 => Self::Wivrn,
|
|
_ => panic!("XRServiceType index out of bounds"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Display for XRServiceType {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.write_str(match self {
|
|
Self::Monado => "Monado",
|
|
Self::Wivrn => "WiVRn",
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub enum ProfileFeatureType {
|
|
Libsurvive,
|
|
Basalt,
|
|
}
|
|
|
|
impl ProfileFeatureType {
|
|
pub fn from_string(s: String) -> Self {
|
|
match s.trim().to_lowercase().as_str() {
|
|
"libsurvive" => Self::Libsurvive,
|
|
"basalt" => Self::Basalt,
|
|
_ => panic!("Unknown profile feature type"),
|
|
}
|
|
}
|
|
|
|
pub fn iter() -> Iter<'static, ProfileFeatureType> {
|
|
[Self::Libsurvive, Self::Basalt].iter()
|
|
}
|
|
}
|
|
|
|
impl Display for ProfileFeatureType {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.write_str(match self {
|
|
Self::Libsurvive => "Libsurvive",
|
|
Self::Basalt => "Basalt",
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct ProfileFeature {
|
|
pub feature_type: ProfileFeatureType,
|
|
pub enabled: bool,
|
|
pub path: Option<String>,
|
|
pub repo: Option<String>,
|
|
}
|
|
|
|
impl Default for ProfileFeature {
|
|
fn default() -> Self {
|
|
Self {
|
|
feature_type: ProfileFeatureType::Libsurvive,
|
|
enabled: false,
|
|
path: None,
|
|
repo: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct ProfileFeatures {
|
|
pub libsurvive: ProfileFeature,
|
|
pub basalt: ProfileFeature,
|
|
pub mercury_enabled: bool,
|
|
}
|
|
|
|
impl Default for ProfileFeatures {
|
|
fn default() -> Self {
|
|
Self {
|
|
libsurvive: ProfileFeature {
|
|
feature_type: ProfileFeatureType::Libsurvive,
|
|
..Default::default()
|
|
},
|
|
basalt: ProfileFeature {
|
|
feature_type: ProfileFeatureType::Basalt,
|
|
..Default::default()
|
|
},
|
|
mercury_enabled: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub enum LighthouseDriver {
|
|
Vive,
|
|
Survive,
|
|
SteamVR,
|
|
}
|
|
|
|
impl Default for LighthouseDriver {
|
|
fn default() -> Self {
|
|
Self::Vive
|
|
}
|
|
}
|
|
|
|
impl LighthouseDriver {
|
|
pub fn from_string(s: String) -> Self {
|
|
match s.trim().to_lowercase().as_str() {
|
|
"vive" => Self::Vive,
|
|
"survive" => Self::Survive,
|
|
"libsurvive" => Self::Survive,
|
|
"steam" => Self::SteamVR,
|
|
"steamvr" => Self::SteamVR,
|
|
_ => Self::Vive,
|
|
}
|
|
}
|
|
|
|
pub fn iter() -> Iter<'static, Self> {
|
|
[Self::Vive, Self::Survive, Self::SteamVR].iter()
|
|
}
|
|
|
|
pub fn as_number(&self) -> u32 {
|
|
match self {
|
|
Self::Vive => 0,
|
|
Self::Survive => 1,
|
|
Self::SteamVR => 2,
|
|
}
|
|
}
|
|
|
|
pub fn from_number(i: u32) -> Self {
|
|
match i {
|
|
0 => Self::Vive,
|
|
1 => Self::Survive,
|
|
2 => Self::SteamVR,
|
|
_ => panic!("LighthouseDriver index out of bounds"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Display for LighthouseDriver {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.write_str(match self {
|
|
Self::Vive => "Vive",
|
|
Self::Survive => "Survive",
|
|
Self::SteamVR => "SteamVR",
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct Profile {
|
|
pub uuid: String,
|
|
pub name: String,
|
|
pub xrservice_type: XRServiceType,
|
|
pub xrservice_path: String,
|
|
pub xrservice_repo: Option<String>,
|
|
pub opencomposite_path: String,
|
|
pub opencomposite_repo: Option<String>,
|
|
pub features: ProfileFeatures,
|
|
pub environment: HashMap<String, String>,
|
|
/** Install prefix */
|
|
pub prefix: String,
|
|
pub can_be_built: bool,
|
|
pub editable: bool,
|
|
pub pull_on_build: bool,
|
|
#[serde(default = "LighthouseDriver::default")]
|
|
/** Only applicable for Monado */
|
|
pub lighthouse_driver: LighthouseDriver,
|
|
}
|
|
|
|
impl Display for Profile {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.write_str(&self.name.to_string())
|
|
}
|
|
}
|
|
|
|
impl Default for Profile {
|
|
fn default() -> Self {
|
|
Self {
|
|
uuid: Uuid::new_v4().to_string(),
|
|
name: "Default profile name".into(),
|
|
xrservice_path: data_monado_path(),
|
|
xrservice_type: XRServiceType::Monado,
|
|
opencomposite_path: data_opencomposite_path(),
|
|
features: ProfileFeatures {
|
|
libsurvive: ProfileFeature {
|
|
feature_type: ProfileFeatureType::Libsurvive,
|
|
..Default::default()
|
|
},
|
|
basalt: ProfileFeature {
|
|
feature_type: ProfileFeatureType::Basalt,
|
|
..Default::default()
|
|
},
|
|
mercury_enabled: false,
|
|
},
|
|
environment: HashMap::new(),
|
|
prefix: format!(
|
|
"{data}/prefixes/default_profile_prefix",
|
|
data = get_data_dir()
|
|
),
|
|
can_be_built: true,
|
|
pull_on_build: true,
|
|
xrservice_repo: None,
|
|
opencomposite_repo: None,
|
|
editable: true,
|
|
lighthouse_driver: LighthouseDriver::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Profile {
|
|
pub fn get_steam_launch_options(&self) -> String {
|
|
vec![
|
|
// format!(
|
|
// "VR_OVERRIDE={opencomp}/build",
|
|
// opencomp = self.opencomposite_path,
|
|
// ),
|
|
format!(
|
|
"XR_RUNTIME_JSON={prefix}/share/openxr/1/openxr_{runtime}.json",
|
|
prefix = match self.prefix.as_str() {
|
|
SYSTEM_PREFIX => BWRAP_SYSTEM_PREFIX,
|
|
other => other,
|
|
},
|
|
runtime = match self.xrservice_type {
|
|
XRServiceType::Monado => "monado",
|
|
XRServiceType::Wivrn => "wivrn",
|
|
}
|
|
),
|
|
format!(
|
|
"PRESSURE_VESSEL_FILESYSTEMS_RW={path}",
|
|
path = get_ipc_file_path(&self.xrservice_type),
|
|
),
|
|
"%command%".into(),
|
|
]
|
|
.join(" ")
|
|
}
|
|
|
|
pub fn get_survive_cli_path(&self) -> Option<String> {
|
|
let path_s = format!("{pfx}/bin/survive-cli", pfx = self.prefix);
|
|
if Path::new(&path_s).is_file() {
|
|
return Some(path_s);
|
|
}
|
|
None
|
|
}
|
|
|
|
pub fn load_profile(path: &String) -> Self {
|
|
let file = File::open(path).expect("Unable to open profile");
|
|
let reader = BufReader::new(file);
|
|
serde_json::from_reader(reader).expect("Faiuled to deserialize profile")
|
|
}
|
|
|
|
pub fn dump_profile(&self, path_s: &String) {
|
|
let writer = get_writer(path_s);
|
|
serde_json::to_writer_pretty(writer, self).expect("Could not write profile")
|
|
}
|
|
|
|
pub fn create_duplicate(&self) -> Self {
|
|
let mut dup = self.clone();
|
|
dup.uuid = Uuid::new_v4().to_string();
|
|
dup.editable = true;
|
|
dup.name = format!("Duplicate of {}", dup.name);
|
|
dup
|
|
}
|
|
|
|
pub fn validate(&self) -> bool {
|
|
!self.name.is_empty()
|
|
&& self.editable
|
|
&& !self.uuid.is_empty()
|
|
&& !self.xrservice_path.is_empty()
|
|
&& !self.prefix.is_empty()
|
|
&& (!self.features.libsurvive.enabled
|
|
|| !self
|
|
.features
|
|
.libsurvive
|
|
.path
|
|
.as_ref()
|
|
.unwrap_or(&"".to_string())
|
|
.is_empty())
|
|
&& (!self.features.basalt.enabled
|
|
|| !self
|
|
.features
|
|
.basalt
|
|
.path
|
|
.as_ref()
|
|
.unwrap_or(&"".to_string())
|
|
.is_empty())
|
|
}
|
|
|
|
pub fn xrservice_binary(&self) -> String {
|
|
match self.xrservice_type {
|
|
XRServiceType::Monado => format!("{pfx}/bin/monado-service", pfx = self.prefix),
|
|
XRServiceType::Wivrn => format!("{pfx}/bin/wivrn-server", pfx = self.prefix),
|
|
}
|
|
}
|
|
|
|
pub fn can_start(&self) -> bool {
|
|
Path::new(&self.xrservice_binary()).is_file()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::collections::HashMap;
|
|
|
|
use crate::profile::{ProfileFeature, ProfileFeatureType, ProfileFeatures, XRServiceType};
|
|
|
|
use super::Profile;
|
|
|
|
#[test]
|
|
fn profile_can_be_loaded() {
|
|
let profile = Profile::load_profile(&"./test/files/profile.json".to_string());
|
|
assert_eq!(profile.name, "Demo profile");
|
|
assert_eq!(profile.xrservice_path, "/home/user/monado");
|
|
assert_eq!(profile.opencomposite_path, "/home/user/opencomposite");
|
|
assert_eq!(profile.prefix, "/home/user/envisionprefix");
|
|
assert_eq!(
|
|
profile.features.libsurvive.path.as_deref(),
|
|
Some("/home/user/libsurvive")
|
|
);
|
|
assert_eq!(profile.features.basalt.path, None);
|
|
assert_eq!(profile.features.libsurvive.enabled, true);
|
|
assert_eq!(profile.features.basalt.enabled, false);
|
|
assert_eq!(profile.features.mercury_enabled, false);
|
|
assert!(profile
|
|
.environment
|
|
.contains_key("XRT_COMPOSITOR_SCALE_PERCENTAGE"));
|
|
assert!(profile.environment.contains_key("XRT_COMPOSITOR_COMPUTE"));
|
|
assert!(profile
|
|
.environment
|
|
.contains_key("SURVIVE_GLOBALSCENESOLVER"));
|
|
}
|
|
|
|
#[test]
|
|
fn profile_can_be_dumped() {
|
|
let mut env = HashMap::new();
|
|
env.insert("XRT_COMPOSITOR_SCALE_PERCENTAGE".into(), "140".into());
|
|
env.insert("XRT_COMPOSITOR_COMPUTE".into(), "1".into());
|
|
let p = Profile {
|
|
uuid: "demo".into(),
|
|
name: "Demo profile".into(),
|
|
xrservice_path: String::from("/home/user/monado"),
|
|
xrservice_type: XRServiceType::Monado,
|
|
opencomposite_path: String::from("/home/user/opencomposite"),
|
|
features: ProfileFeatures {
|
|
libsurvive: ProfileFeature {
|
|
feature_type: ProfileFeatureType::Libsurvive,
|
|
enabled: true,
|
|
path: Some(String::from("/home/user/libsurvive")),
|
|
repo: None,
|
|
},
|
|
basalt: ProfileFeature {
|
|
feature_type: ProfileFeatureType::Basalt,
|
|
..Default::default()
|
|
},
|
|
mercury_enabled: false,
|
|
},
|
|
environment: env,
|
|
prefix: String::from("/home/user/envisionprefix"),
|
|
editable: true,
|
|
..Default::default()
|
|
};
|
|
let fpath = String::from("./target/testout/testprofile.json");
|
|
p.dump_profile(&fpath);
|
|
let loaded = Profile::load_profile(&fpath);
|
|
assert_eq!(loaded.name, "Demo profile");
|
|
assert_eq!(
|
|
loaded.features.libsurvive.path,
|
|
Some(String::from("/home/user/libsurvive"))
|
|
);
|
|
assert_eq!(
|
|
loaded
|
|
.environment
|
|
.get("XRT_COMPOSITOR_COMPUTE")
|
|
.expect("Key XRT_COMPOSITOR_COMPUTE not found"),
|
|
"1"
|
|
);
|
|
}
|
|
}
|