diff --git a/Cargo.lock b/Cargo.lock index 312a7aa..cb2d639 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -445,6 +445,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "delicious-adwaita" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e53548c789a95211e0ce6d26c213067002b9b4360f8de69046d84de78ad9da3f" +dependencies = [ + "gtk4", + "libadwaita", +] + [[package]] name = "deranged" version = "0.3.11" @@ -563,10 +573,11 @@ dependencies = [ [[package]] name = "envision" -version = "3.0.0" +version = "3.1.1" dependencies = [ "anyhow", "ash", + "delicious-adwaita", "gettext-rs", "git2", "gtk4", @@ -1073,9 +1084,9 @@ dependencies = [ [[package]] name = "gtk4" -version = "0.9.4" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9376d14d7e33486c54823a42bef296e882b9f25cb4c52b52f4d1d57bbadb5b6d" +checksum = "af1c491051f030994fd0cde6f3c44f3f5640210308cff1298c7673c47408091d" dependencies = [ "cairo-rs", "field-offset", @@ -1106,9 +1117,9 @@ dependencies = [ [[package]] name = "gtk4-sys" -version = "0.9.4" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e653b0a9001ba9be1ffddb9373bfe9a111f688222f5aeee2841481300d91b55a" +checksum = "41e03b01e54d77c310e1d98647d73f996d04b2f29b9121fe493ea525a7ec03d6" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -1523,9 +1534,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libadwaita" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8611ee9fb85e7606c362b513afcaf5b59853f79e4d98caaaf581d99465014247" +checksum = "500135d29c16aabf67baafd3e7741d48e8b8978ca98bac39e589165c8dc78191" dependencies = [ "gdk4", "gio", diff --git a/Cargo.toml b/Cargo.toml index f41c7f6..4ecd109 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "envision" -version = "3.0.0" +version = "3.1.1" edition = "2021" authors = [ "Gabriele Musco ", @@ -44,3 +44,4 @@ tracing-subscriber = { version = "0.3.19", features = ["env-filter", "json"] } tracing = "0.1.41" tracing-appender = "0.2.3" serde_yaml = "0.9.34" +delicious-adwaita = { version = "0.3.0", features = ["all_themes"] } diff --git a/data/org.gabmus.envision.metainfo.xml.in.in b/data/org.gabmus.envision.metainfo.xml.in.in index cbe2b3a..76b4dd4 100644 --- a/data/org.gabmus.envision.metainfo.xml.in.in +++ b/data/org.gabmus.envision.metainfo.xml.in.in @@ -30,6 +30,43 @@ @REPO_URL@/issues + + +

Fixes

+
    +
  • add libusb and libusb-dev deps
  • +
  • Revert "disable and blacklist wayvr dashboard plugin"
  • +
+
+
+ + +

What's new

+
    +
  • don't set openvrpaths as read only during profile startup
  • +
  • small design changes to build window ui
  • +
  • add support for vapor openvr compatibility module
  • +
  • remove monado vulkan layers check for nvidia
  • +
+

Fixes

+
    +
  • disable and blacklist wayvr dashboard plugin
  • +
  • monado dependencies: use wayland-protocols-devel on Fedora
  • +
+

Other changes

+
    +
  • clippy
  • +
+
+
+ + +

Fixes

+
    +
  • libnotify headers path in wivrn depcheck
  • +
+
+

Breaking changes

diff --git a/dist/arch/PKGBUILD b/dist/arch/PKGBUILD index 3ff8a64..ee1a4b2 100644 --- a/dist/arch/PKGBUILD +++ b/dist/arch/PKGBUILD @@ -33,7 +33,6 @@ makedepends=( ) optdepends=( 'libudev0-shim: steamvr_lh lighthouse driver support' - 'monado-vulkan-layers-git: Vulkan layers for NVIDIA users' ) provides=(envision) conflicts=(envision) diff --git a/meson.build b/meson.build index 8a4176b..d58f4ed 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'envision', 'rust', - version: '3.0.0', # version number row + version: '3.1.1', # version number row meson_version: '>= 0.59', license: 'AGPL-3.0-or-later', ) diff --git a/scripts/build_mercury.sh b/scripts/build_mercury.sh index b3db66a..932d343 100755 --- a/scripts/build_mercury.sh +++ b/scripts/build_mercury.sh @@ -2,57 +2,6 @@ set -ev -PREFIX=$1 - -CACHE_DIR=$2 - -if [[ -z $PREFIX ]] || [[ -z $CACHE_DIR ]]; then - echo "Usage: $0 PREFIX CACHE_DIR" - exit 1 -fi - -ONNX_RELEASES=$(curl -sSL "https://api.github.com/repos/microsoft/onnxruntime/releases") -NUM_RELEASES=$(echo "$ONNX_RELEASES" | jq -r '[ select (.[]!=null) ] | length') - -for (( IDX=0; IDX VecDeque { let mut jobs = VecDeque::::new(); @@ -40,11 +43,24 @@ pub fn get_build_basalt_jobs(profile: &Profile, clean_build: bool) -> VecDeque = HashMap::new(); for (k, v) in [ - ("CMAKE_BUILD_PARALLEL_LEVEL", "2"), - ("CMAKE_BUILD_TYPE", "RelWithDebInfo"), - ("BUILD_TESTS", "off"), + // The basalt build uses a lot of RAM, so we have to limit the number of + // build processes to not starve the system of memory + // Limit to 6 build processes at most + ( + "CMAKE_BUILD_PARALLEL_LEVEL", + std::cmp::min( + 6, + std::thread::available_parallelism() + .map(NonZero::get) + .unwrap_or(2), + ) + .to_string(), + ), + ("CMAKE_BUILD_TYPE", "RelWithDebInfo".into()), + ("CMAKE_POLICY_VERSION_MINIMUM", "3.5".into()), + ("BUILD_TESTS", "off".into()), ] { - cmake_env.insert(k.to_string(), v.to_string()); + cmake_env.insert(k.to_string(), v); } cmake_env }), diff --git a/src/builders/build_mercury.rs b/src/builders/build_mercury.rs index 36b5c23..28185d1 100644 --- a/src/builders/build_mercury.rs +++ b/src/builders/build_mercury.rs @@ -1,11 +1,8 @@ use std::collections::VecDeque; -use crate::{ - constants::pkg_data_dir, paths::get_cache_dir, profile::Profile, termcolor::TermColor, - ui::job_worker::job::WorkerJob, -}; +use crate::{constants::pkg_data_dir, termcolor::TermColor, ui::job_worker::job::WorkerJob}; -pub fn get_build_mercury_jobs(profile: &Profile) -> VecDeque { +pub fn get_build_mercury_jobs() -> VecDeque { let mut jobs = VecDeque::new(); jobs.push_back(WorkerJob::new_printer( "Building Mercury...", @@ -17,10 +14,7 @@ pub fn get_build_mercury_jobs(profile: &Profile) -> VecDeque { .join("scripts/build_mercury.sh") .to_string_lossy() .to_string(), - Some(vec![ - profile.prefix.to_string_lossy().to_string(), - get_cache_dir().to_string_lossy().to_string(), - ]), + None, )); jobs diff --git a/src/builders/build_vapor.rs b/src/builders/build_vapor.rs new file mode 100644 index 0000000..5a05932 --- /dev/null +++ b/src/builders/build_vapor.rs @@ -0,0 +1,68 @@ +use crate::{ + build_tools::{cmake::Cmake, git::Git}, + profile::Profile, + termcolor::TermColor, + ui::job_worker::job::WorkerJob, + util::file_utils::rm_rf, +}; +use std::{ + collections::{HashMap, VecDeque}, + path::Path, +}; + +pub fn get_build_vapor_jobs(profile: &Profile, clean_build: bool) -> VecDeque { + let mut jobs = VecDeque::::new(); + jobs.push_back(WorkerJob::new_printer( + "Building VapoR...", + Some(TermColor::Blue), + )); + + let git = Git { + repo: profile + .ovr_comp + .repo + .as_ref() + .unwrap_or(&"https://github.com/micheal65536/VapoR.git".into()) + .clone(), + dir: profile.ovr_comp.path.clone(), + branch: profile + .ovr_comp + .branch + .as_ref() + .unwrap_or(&"master".into()) + .clone(), + }; + + jobs.extend(git.get_pre_build_jobs(profile.pull_on_build)); + + let build_dir = profile.ovr_comp.path.join("build"); + let install_dir = build_dir.join("install_pfx"); + let cmake = Cmake { + env: None, + vars: Some({ + let mut cmake_vars: HashMap = HashMap::new(); + for (k, v) in [ + ("VAPOR_LOG_SILENT", "ON"), + ("USE_SYSTEM_OPENXR", "OFF"), + ("CMAKE_BUILD_TYPE", "RelWithDebInfo"), + ] { + cmake_vars.insert(k.to_string(), v.to_string()); + } + cmake_vars.insert( + "CMAKE_INSTALL_PREFIX".into(), + install_dir.to_string_lossy().to_string(), + ); + cmake_vars + }), + source_dir: profile.ovr_comp.path.clone(), + build_dir: build_dir.clone(), + }; + if !Path::new(&build_dir).is_dir() || clean_build { + rm_rf(&build_dir); + jobs.push_back(cmake.get_prepare_job()); + } + jobs.push_back(cmake.get_build_job()); + jobs.push_back(cmake.get_install_job()); + + jobs +} diff --git a/src/builders/mod.rs b/src/builders/mod.rs index 1f66748..9d57245 100644 --- a/src/builders/mod.rs +++ b/src/builders/mod.rs @@ -4,5 +4,6 @@ pub mod build_mercury; pub mod build_monado; pub mod build_opencomposite; pub mod build_openhmd; +pub mod build_vapor; pub mod build_wivrn; pub mod build_xrizer; diff --git a/src/config.rs b/src/config.rs index 18cd864..80cf0f5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -45,6 +45,10 @@ const fn default_win_size() -> [i32; 2] { DEFAULT_WIN_SIZE } +fn default_theme_name() -> String { + "Follow system".into() +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Config { pub selected_profile_uuid: String, @@ -54,6 +58,8 @@ pub struct Config { pub win_size: [i32; 2], #[serde(default)] pub plugins: HashMap, + #[serde(default = "default_theme_name")] + pub theme_name: String, } impl Default for Config { @@ -65,6 +71,7 @@ impl Default for Config { user_profiles: Vec::default(), win_size: DEFAULT_WIN_SIZE, plugins: HashMap::default(), + theme_name: default_theme_name(), } } } diff --git a/src/depcheck/basalt_deps.rs b/src/depcheck/basalt_deps.rs index 2a05826..00a29b6 100644 --- a/src/depcheck/basalt_deps.rs +++ b/src/depcheck/basalt_deps.rs @@ -1,7 +1,7 @@ use super::{ boost_deps::boost_deps, common::{dep_cmake, dep_eigen, dep_gpp, dep_libgl, dep_ninja, dep_opencv}, - DepType, Dependency, DependencyCheckResult, + DepType, DepcheckResultGetMissing, Dependency, DependencyCheckResult, }; use crate::linux_distro::LinuxDistro; use std::collections::HashMap; @@ -181,9 +181,5 @@ pub fn check_basalt_deps() -> Vec { } pub fn get_missing_basalt_deps() -> Vec { - check_basalt_deps() - .iter() - .filter(|res| !res.found) - .map(|res| res.dependency.clone()) - .collect() + check_basalt_deps().filter_missing_deps() } diff --git a/src/depcheck/common.rs b/src/depcheck/common.rs index 56ac4ef..901d0ff 100644 --- a/src/depcheck/common.rs +++ b/src/depcheck/common.rs @@ -303,3 +303,35 @@ pub fn dep_adb() -> Dependency { ]), } } + +pub fn dep_getcap_setcap() -> Dependency { + Dependency { + name: "libcap".into(), + dep_type: DepType::Executable, + filename: "setcap".into(), + packages: HashMap::from([ + (LinuxDistro::Arch, "libcap".into()), + (LinuxDistro::Debian, "libcap2-bin".into()), + (LinuxDistro::Fedora, "libcap".into()), + (LinuxDistro::Alpine, "libcap".into()), + (LinuxDistro::Gentoo, "sys-libs/libcap".into()), + (LinuxDistro::Suse, "libcap-progs".into()), + ]), + } +} + +pub fn dep_glslc() -> Dependency { + Dependency { + name: "glslc".into(), + dep_type: DepType::Executable, + filename: "glslc".into(), + packages: HashMap::from([ + (LinuxDistro::Arch, "shaderc".into()), + (LinuxDistro::Debian, "glslc".into()), + (LinuxDistro::Fedora, "glslc".into()), + (LinuxDistro::Alpine, "shaderc".into()), + (LinuxDistro::Gentoo, "media-libs/shaderc".into()), + (LinuxDistro::Suse, "shaderc".into()), + ]), + } +} diff --git a/src/depcheck/libsurvive_deps.rs b/src/depcheck/libsurvive_deps.rs index c9e8d9c..46eec55 100644 --- a/src/depcheck/libsurvive_deps.rs +++ b/src/depcheck/libsurvive_deps.rs @@ -1,6 +1,6 @@ use super::{ common::{dep_cmake, dep_eigen, dep_gcc, dep_git, dep_gpp, dep_ninja}, - Dependency, DependencyCheckResult, + DepcheckResultGetMissing, Dependency, DependencyCheckResult, }; fn libsurvive_deps() -> Vec { @@ -19,9 +19,5 @@ pub fn check_libsurvive_deps() -> Vec { } pub fn get_missing_libsurvive_deps() -> Vec { - check_libsurvive_deps() - .iter() - .filter(|res| !res.found) - .map(|res| res.dependency.clone()) - .collect() + check_libsurvive_deps().filter_missing_deps() } diff --git a/src/depcheck/mercury_deps.rs b/src/depcheck/mercury_deps.rs index cad3c80..d8c61b5 100644 --- a/src/depcheck/mercury_deps.rs +++ b/src/depcheck/mercury_deps.rs @@ -1,10 +1,27 @@ -use super::{common::dep_opencv, DepType, Dependency, DependencyCheckResult}; +use super::{ + common::dep_opencv, DepType, DepcheckResultGetMissing, Dependency, DependencyCheckResult, +}; use crate::linux_distro::LinuxDistro; use std::collections::HashMap; fn mercury_deps() -> Vec { vec![ dep_opencv(), + Dependency { + name: "onnxruntime-dev".into(), + dep_type: DepType::Include, + filename: "onnxruntime/onnxruntime_c_api.h".into(), + packages: HashMap::from([ + (LinuxDistro::Arch, "onnxruntime".into()), + (LinuxDistro::Debian, "libonnxruntime-dev".into()), + (LinuxDistro::Fedora, "onnxruntime-devel".into()), + // alpine doesn't seem to have the package + // (LinuxDistro::Alpine, "".into()), + (LinuxDistro::Gentoo, "sci-ml/onnx".into()), + // opensuse doesn't seem to have the package + // (LinuxDistro::Suse, "".into()), + ]), + }, Dependency { name: "jq".into(), dep_type: DepType::Executable, @@ -39,9 +56,5 @@ pub fn check_mercury_deps() -> Vec { } pub fn get_missing_mercury_deps() -> Vec { - check_mercury_deps() - .iter() - .filter(|res| !res.found) - .map(|res| res.dependency.clone()) - .collect() + check_mercury_deps().filter_missing_deps() } diff --git a/src/depcheck/mod.rs b/src/depcheck/mod.rs index 002a8c8..f2565eb 100644 --- a/src/depcheck/mod.rs +++ b/src/depcheck/mod.rs @@ -6,6 +6,7 @@ pub mod mercury_deps; pub mod monado_deps; pub mod openhmd_deps; pub mod wivrn_deps; +pub mod xrizer_deps; use crate::linux_distro::LinuxDistro; use std::{collections::HashMap, env, fmt::Display, path::Path}; @@ -108,6 +109,24 @@ impl Display for DependencyCheckResult { } } +pub trait DepcheckResultGetMissing { + fn filter_missing_deps(self) -> Vec; +} + +impl DepcheckResultGetMissing for Vec { + fn filter_missing_deps(self) -> Vec { + self.into_iter() + .filter_map(|res| { + if !res.found { + Some(res.dependency) + } else { + None + } + }) + .collect() + } +} + fn shared_obj_paths() -> Vec { vec![ "/lib".into(), @@ -117,6 +136,24 @@ fn shared_obj_paths() -> Vec { "/usr/local/lib64".into(), "/usr/lib/x86_64-linux-gnu".into(), "/usr/lib/aarch64-linux-gnu".into(), + // Debian puts libclang in /usr/lib/llvm-[llvm major version]/lib. + "/usr/lib/llvm-15/lib".into(), + "/usr/lib/llvm-16/lib".into(), + "/usr/lib/llvm-19/lib".into(), + // Fedora puts libclang in /usr/lib64/llvm[llvm major version]/lib as well as /usr/lib64. + "/usr/lib64/llvm15/lib".into(), + "/usr/lib64/llvm16/lib".into(), + "/usr/lib64/llvm17/lib".into(), + "/usr/lib64/llvm18/lib".into(), + "/usr/lib64/llvm19/lib".into(), + "/usr/lib64/llvm20/lib".into(), + // Gentoo puts libclang in /usr/lib/llvm/[llvm major version]/lib64. + "/usr/lib/llvm/15/lib64".into(), + "/usr/lib/llvm/16/lib64".into(), + "/usr/lib/llvm/17/lib64".into(), + "/usr/lib/llvm/18/lib64".into(), + "/usr/lib/llvm/19/lib64".into(), + "/usr/lib/llvm/20/lib64".into(), "/lib/x86_64-linux-gnu".into(), "/lib/aarch64-linux-gnu".into(), "/app/lib".into(), @@ -140,6 +177,8 @@ fn include_paths() -> Vec { "/usr/include/ffmpeg/libpostproc".into(), "/usr/include/ffmpeg/libswresample".into(), "/usr/include/ffmpeg/libswscale".into(), + // opensuse puts wayland-client.h here + "/usr/include/wayland".into(), ] } diff --git a/src/depcheck/monado_deps.rs b/src/depcheck/monado_deps.rs index 93055d3..f9e1b9b 100644 --- a/src/depcheck/monado_deps.rs +++ b/src/depcheck/monado_deps.rs @@ -4,9 +4,12 @@ use super::{ dep_libgl, dep_libudev, dep_libx11, dep_libxcb, dep_ninja, dep_openxr, dep_vulkan_headers, dep_vulkan_icd_loader, }, - DepType, Dependency, DependencyCheckResult, + DepType, DepcheckResultGetMissing, Dependency, DependencyCheckResult, +}; +use crate::{ + depcheck::common::{dep_glslc, dep_libxrandr}, + linux_distro::LinuxDistro, }; -use crate::{depcheck::common::dep_libxrandr, linux_distro::LinuxDistro}; use std::collections::HashMap; fn monado_deps() -> Vec { @@ -37,7 +40,7 @@ fn monado_deps() -> Vec { packages: HashMap::from([ (LinuxDistro::Arch, "wayland-protocols".into()), (LinuxDistro::Debian, "wayland-protocols".into()), - (LinuxDistro::Fedora, "wayland-protocols".into()), + (LinuxDistro::Fedora, "wayland-protocols-devel".into()), (LinuxDistro::Gentoo, "dev-libs/wayland-protocols".into()), (LinuxDistro::Suse, "wayland-protocols-devel".into()), ]), @@ -60,19 +63,7 @@ fn monado_deps() -> Vec { dep_ninja(), dep_gcc(), dep_gpp(), - Dependency { - name: "glslc".into(), - dep_type: DepType::Executable, - filename: "glslc".into(), - packages: HashMap::from([ - (LinuxDistro::Arch, "shaderc".into()), - (LinuxDistro::Debian, "glslc".into()), - (LinuxDistro::Fedora, "glslc".into()), - (LinuxDistro::Alpine, "shaderc".into()), - (LinuxDistro::Gentoo, "media-libs/shaderc".into()), - (LinuxDistro::Suse, "shaderc".into()), - ]), - }, + dep_glslc(), dep_glslang_validator(), Dependency { name: "sdl2".into(), @@ -83,10 +74,34 @@ fn monado_deps() -> Vec { (LinuxDistro::Debian, "libsdl2-dev".into()), (LinuxDistro::Fedora, "SDL2-devel".into()), (LinuxDistro::Gentoo, "media-libs/libsdl2".into()), - (LinuxDistro::Suse, "SDL2-devel".into()), + (LinuxDistro::Suse, "sdl2-compat-devel".into()), ]), }, dep_libudev(), + Dependency { + name: "libusb".into(), + dep_type: DepType::SharedObject, + filename: "libusb-1.0.so".into(), + packages: HashMap::from([ + (LinuxDistro::Arch, "libusb".into()), + (LinuxDistro::Debian, "libusb-1.0-0".into()), + (LinuxDistro::Fedora, "libusb1".into()), + (LinuxDistro::Gentoo, "dev-libs/libusb".into()), + (LinuxDistro::Suse, "libusb-1_0-0".into()), + ]), + }, + Dependency { + name: "libusb-dev".into(), + dep_type: DepType::Include, + filename: "libusb-1.0/libusb.h".into(), + packages: HashMap::from([ + (LinuxDistro::Arch, "libusb".into()), + (LinuxDistro::Debian, "libusb-1.0-0-dev".into()), + (LinuxDistro::Fedora, "libusb1-devel".into()), + (LinuxDistro::Gentoo, "dev-libs/libusb".into()), + (LinuxDistro::Suse, "libusb-1_0-devel".into()), + ]), + }, Dependency { name: "mesa-common-dev".into(), dep_type: DepType::Include, @@ -107,9 +122,5 @@ pub fn check_monado_deps() -> Vec { } pub fn get_missing_monado_deps() -> Vec { - check_monado_deps() - .iter() - .filter(|res| !res.found) - .map(|res| res.dependency.clone()) - .collect() + check_monado_deps().filter_missing_deps() } diff --git a/src/depcheck/openhmd_deps.rs b/src/depcheck/openhmd_deps.rs index 819324f..d31d013 100644 --- a/src/depcheck/openhmd_deps.rs +++ b/src/depcheck/openhmd_deps.rs @@ -1,6 +1,6 @@ use super::{ common::{dep_gcc, dep_git, dep_gpp, dep_ninja}, - Dependency, DependencyCheckResult, + DepcheckResultGetMissing, Dependency, DependencyCheckResult, }; use crate::linux_distro::LinuxDistro; use std::collections::HashMap; @@ -31,9 +31,5 @@ pub fn check_openhmd_deps() -> Vec { } pub fn get_missing_openhmd_deps() -> Vec { - check_openhmd_deps() - .iter() - .filter(|res| !res.found) - .map(|res| res.dependency.clone()) - .collect() + check_openhmd_deps().filter_missing_deps() } diff --git a/src/depcheck/wivrn_deps.rs b/src/depcheck/wivrn_deps.rs index fc704d9..9126272 100644 --- a/src/depcheck/wivrn_deps.rs +++ b/src/depcheck/wivrn_deps.rs @@ -4,7 +4,7 @@ use super::{ dep_libudev, dep_libx11, dep_libxcb, dep_ninja, dep_openxr, dep_vulkan_headers, dep_vulkan_icd_loader, }, - DepType, Dependency, DependencyCheckResult, + DepType, DepcheckResultGetMissing, Dependency, DependencyCheckResult, }; use crate::{ depcheck::common::{dep_libgl, dep_libxrandr}, @@ -253,7 +253,7 @@ fn wivrn_deps() -> Vec { Dependency { name: "libnotify-dev".into(), dep_type: DepType::Include, - filename: "openssl/ssl3.h".into(), + filename: "libnotify/notify.h".into(), packages: HashMap::from([ (LinuxDistro::Arch, "libnotify".into()), (LinuxDistro::Alpine, "libnotify-dev".into()), @@ -270,9 +270,5 @@ pub fn check_wivrn_deps() -> Vec { } pub fn get_missing_wivrn_deps() -> Vec { - check_wivrn_deps() - .iter() - .filter(|res| !res.found) - .map(|res| res.dependency.clone()) - .collect() + check_wivrn_deps().filter_missing_deps() } diff --git a/src/depcheck/xrizer_deps.rs b/src/depcheck/xrizer_deps.rs new file mode 100644 index 0000000..49d9354 --- /dev/null +++ b/src/depcheck/xrizer_deps.rs @@ -0,0 +1,65 @@ +use super::{DepType, DepcheckResultGetMissing, Dependency, DependencyCheckResult}; +use crate::{depcheck::common::dep_glslc, linux_distro::LinuxDistro}; +use std::collections::HashMap; + +fn xrizer_deps() -> Vec { + vec![ + dep_glslc(), + Dependency { + name: "cargo".into(), + dep_type: DepType::Executable, + filename: "cargo".into(), + packages: HashMap::from([ + (LinuxDistro::Arch, "rust".into()), + (LinuxDistro::Debian, "cargo".into()), + (LinuxDistro::Fedora, "cargo".into()), + (LinuxDistro::Alpine, "cargo".into()), + (LinuxDistro::Suse, "cargo".into()), + ]), + }, + Dependency { + name: "libxcb-glx".into(), + dep_type: DepType::Include, + filename: "xcb/glx.h".into(), + packages: HashMap::from([ + (LinuxDistro::Arch, "libxcb".into()), + (LinuxDistro::Debian, "libxcb-glx0-dev".into()), + (LinuxDistro::Fedora, "libxcb-devel".into()), + (LinuxDistro::Gentoo, "x11-libs/libxcb".into()), + (LinuxDistro::Suse, "libxcb-devel".into()), + ]), + }, + Dependency { + name: "libclang".into(), + dep_type: DepType::SharedObject, + filename: "libclang.so".into(), + packages: HashMap::from([ + (LinuxDistro::Arch, "clang".into()), + (LinuxDistro::Debian, "libclang-19-dev".into()), + (LinuxDistro::Fedora, "clang19-devel".into()), + (LinuxDistro::Gentoo, "llvm-core/clang-runtime".into()), + (LinuxDistro::Suse, "clang19-devel".into()), + ]), + }, + Dependency { + name: "wayland-dev".into(), + dep_type: DepType::Include, + filename: "wayland-client.h".into(), + packages: HashMap::from([ + (LinuxDistro::Arch, "wayland".into()), + (LinuxDistro::Debian, "libwayland-dev".into()), + (LinuxDistro::Fedora, "wayland-devel".into()), + (LinuxDistro::Gentoo, "dev-libs/wayland".into()), + (LinuxDistro::Suse, "wayland-devel".into()), + ]), + }, + ] +} + +pub fn check_xrizer_deps() -> Vec { + Dependency::check_many(xrizer_deps()) +} + +pub fn get_missing_xrizer_deps() -> Vec { + check_xrizer_deps().filter_missing_deps() +} diff --git a/src/downloader.rs b/src/downloader.rs index bb9a4b7..3b1b16b 100644 --- a/src/downloader.rs +++ b/src/downloader.rs @@ -13,7 +13,7 @@ const CHUNK_SIZE: usize = 1024; fn headers() -> HeaderMap { let mut headers = HeaderMap::new(); - headers.insert(USER_AGENT, format!("{}/1.0", APP_ID).parse().unwrap()); + headers.insert(USER_AGENT, format!("{APP_ID}/1.0").parse().unwrap()); headers } diff --git a/src/env_var_descriptions.rs b/src/env_var_descriptions.rs index ef92101..84a404b 100644 --- a/src/env_var_descriptions.rs +++ b/src/env_var_descriptions.rs @@ -65,7 +65,7 @@ fn env_var_descriptions() -> Vec<(&'static str, &'static str)> { fn env_var_descriptions_as_paragraph() -> String { ENV_VAR_DESCRIPTIONS .iter() - .map(|(k, v)| format!("{}\n{}", k, v)) + .map(|(k, v)| format!("{k}\n{v}")) .collect::>() .join("\n\n") } diff --git a/src/file_builders/openvrpaths_vrpath.rs b/src/file_builders/openvrpaths_vrpath.rs index 7bd431b..1b888c2 100644 --- a/src/file_builders/openvrpaths_vrpath.rs +++ b/src/file_builders/openvrpaths_vrpath.rs @@ -85,6 +85,7 @@ fn build_steam_openvrpaths() -> OpenVrPaths { } pub fn set_current_openvrpaths_to_steam() -> anyhow::Result<()> { + // removing readonly flag just in case, remove this line in the future set_file_readonly(&get_openvrpaths_vrpath_path(), false)?; dump_current_openvrpaths(&build_steam_openvrpaths())?; Ok(()) @@ -104,18 +105,17 @@ pub fn build_profile_openvrpaths(profile: &Profile) -> OpenVrPaths { pub fn set_current_openvrpaths_to_profile(profile: &Profile) -> anyhow::Result<()> { let dest = get_openvrpaths_vrpath_path(); + // removing readonly flag just in case, remove this line in the future set_file_readonly(&dest, false)?; backup_steam_openvrpaths(); dump_current_openvrpaths(&build_profile_openvrpaths(profile))?; - set_file_readonly(&dest, true)?; Ok(()) } #[cfg(test)] mod tests { - use std::path::Path; - use super::{dump_openvrpaths_to_path, get_openvrpaths_from_path, OpenVrPaths}; + use std::path::Path; #[test] fn can_read_openvrpaths_vrpath_steamvr() { diff --git a/src/file_builders/wivrn_config.rs b/src/file_builders/wivrn_config.rs index 53c966e..16a8b46 100644 --- a/src/file_builders/wivrn_config.rs +++ b/src/file_builders/wivrn_config.rs @@ -143,7 +143,7 @@ pub struct WivrnConfig { pub encoders: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub application: Option, - #[serde(default)] + #[serde(default, rename = "tcp-only")] pub tcp_only: bool, /// contains unknown fields #[serde(flatten)] diff --git a/src/gpu_profile.rs b/src/gpu_profile.rs index 5a57c22..98eea11 100644 --- a/src/gpu_profile.rs +++ b/src/gpu_profile.rs @@ -116,7 +116,7 @@ fn list_gpus() -> Vec { for i in 0..5 { // arbitrary range, find a better way - let card_dir = PathBuf::from(format!("/sys/class/drm/card{}", i)); + let card_dir = PathBuf::from(format!("/sys/class/drm/card{i}")); let vendor_file = card_dir.join("device/vendor"); if let Some(mut reader) = get_reader(&vendor_file) { let mut buf = String::new(); diff --git a/src/linux_distro.rs b/src/linux_distro.rs index 7bdfe0b..2fcd76a 100644 --- a/src/linux_distro.rs +++ b/src/linux_distro.rs @@ -49,13 +49,13 @@ impl LinuxDistro { Ok(_) if buf.starts_with("PRETTY_NAME=\"") => { return buf .split('=') - .last() + .next_back() .map(|b| b.trim().trim_matches('"').trim().to_string()); } Ok(_) if buf.starts_with("NAME=\"") => { name = buf .split('=') - .last() + .next_back() .map(|b| b.trim().trim_matches('"').trim().to_string()); } _ => {} @@ -79,7 +79,7 @@ impl LinuxDistro { { let name = buf .split('=') - .last() + .next_back() .unwrap_or_default() .trim() .trim_matches('"') @@ -115,6 +115,7 @@ impl LinuxDistro { || s.contains("steamos") || s.contains("steam os") || s.contains("endeavour") + || s.contains("cachyos") || s.contains("garuda") { return Some(Self::Arch); @@ -150,7 +151,33 @@ impl LinuxDistro { Self::Alpine => format!("sudo apk add {}", packages.join(" ")), Self::Debian => format!("sudo apt install {}", packages.join(" ")), Self::Gentoo => format!("sudo emerge -av {}", packages.join(" ")), - Self::Suse => format!("sudo zypper install {}", packages.join(" ")), + Self::Suse => { + let mut opi_pkgs = Vec::new(); + let mut zypper_pkgs = Vec::new(); + for pkg in packages { + if ["OpenXR-SDK-devel"].contains(&pkg.as_str()) { + opi_pkgs.push(pkg.clone()); + } else { + zypper_pkgs.push(pkg.clone()); + } + } + [ + if opi_pkgs.is_empty() { + None + } else { + Some(format!("opi {}", opi_pkgs.join(" "))) + }, + if zypper_pkgs.is_empty() { + None + } else { + Some(format!("sudo zypper install {}", zypper_pkgs.join(" "))) + }, + ] + .iter() + .filter_map(|c| c.clone()) + .collect::>() + .join(" && ") + } Self::Fedora => { let mut install_rpmfusion_cmd: Option = None; let mut swap_ffmpeg_cmd: Option = None; @@ -190,9 +217,9 @@ impl LinuxDistro { #[cfg(test)] mod tests { - use std::path::Path; - use super::LinuxDistro; + use crate::depcheck::common::{dep_openxr, dep_pkexec, dep_vulkan_icd_loader}; + use std::path::Path; #[test] fn can_detect_arch_linux_from_etc_os_release() { @@ -203,4 +230,34 @@ mod tests { Some(LinuxDistro::Arch) ) } + + #[test] + fn can_account_for_opensuse_opi_packages() { + assert_eq!( + LinuxDistro::Suse + .install_command( + &[dep_openxr(), dep_vulkan_icd_loader()] + .iter() + .map(|dep| dep.package_name_for_distro(Some(&LinuxDistro::Suse))) + .collect::>() + ) + .as_str(), + "opi OpenXR-SDK-devel && sudo zypper install vulkan-devel" + ) + } + + #[test] + fn opensuse_opi_does_not_interfere_if_not_needed() { + assert_eq!( + LinuxDistro::Suse + .install_command( + &[dep_pkexec(), dep_vulkan_icd_loader()] + .iter() + .map(|dep| dep.package_name_for_distro(Some(&LinuxDistro::Suse))) + .collect::>() + ) + .as_str(), + "sudo zypper install polkit vulkan-devel" + ) + } } diff --git a/src/main.rs b/src/main.rs index bb72425..64a6e59 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,9 +11,16 @@ use relm4::{ gtk::{self, gdk, gio, glib, prelude::*}, MessageBroker, RelmApp, }; -use std::env; -use steam_linux_runtime_injector::restore_runtime_entrypoint; -use tracing::warn; +use std::{ + env, + fs::{read_dir, remove_file}, + os::unix::fs::MetadataExt, + path::{Path, PathBuf}, +}; +use steam_linux_runtime_injector::{ + restore_sniper_runtime_entrypoint, restore_soldier_runtime_entrypoint, +}; +use tracing::{error, warn}; use tracing_subscriber::{ filter::LevelFilter, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer, }; @@ -63,7 +70,50 @@ fn restore_steam_xr_files() { } } } - restore_runtime_entrypoint(); + restore_sniper_runtime_entrypoint(); + restore_soldier_runtime_entrypoint(); +} + +const LOGS_MAX_SIZE_BYTES: u64 = 1000000000; // 1GB + +fn remove_old_logs(dir: &Path, log_files: Option>) -> anyhow::Result<()> { + let log_files: Vec = log_files + .map::>, _>(Ok) + .unwrap_or_else(|| { + let mut files: Vec = read_dir(dir)? + .filter_map(|de| { + let p = de.ok()?.path(); + if p.is_file() && !p.is_symlink() { + Some(p) + } else { + None + } + }) + .collect(); + files.sort_unstable(); + Ok(files) + })?; + let total_size = log_files + .iter() + .filter_map(|p| Some(p.metadata().ok()?.size())) + .reduce(u64::saturating_add) + .unwrap_or(0); + // if size is under threshold, finish + if total_size < LOGS_MAX_SIZE_BYTES { + return Ok(()); + } + // keep a minimum of 3 logs + if log_files.len() <= 3 { + return Ok(()); + } + + remove_file(log_files.first().ok_or_else(|| + anyhow::Error::msg( + "Could not get first item in log files list, but they should be more than 3! This is a bug!" + ) + )?)?; + + remove_old_logs(dir, Some(log_files)) } fn main() -> Result<()> { @@ -71,6 +121,8 @@ fn main() -> Result<()> { panic!("{APP_NAME} cannot run as root"); } restore_steam_xr_files(); + // deferring error logging for this since tracing isn't initialized yet + let old_logs_removal_res = remove_old_logs(&get_logs_dir(), None); let rolling_log_writer = tracing_appender::rolling::daily(get_logs_dir(), "log"); let (non_blocking_appender, _appender_guard) = @@ -90,6 +142,10 @@ fn main() -> Result<()> { ) .init(); + if let Err(e) = old_logs_removal_res { + error!("Failed to remove old log files: {e}"); + } + // Prepare i18n gettextrs::setlocale(LocaleCategory::LcAll, ""); gettextrs::bindtextdomain(GETTEXT_PACKAGE, LOCALE_DIR).expect("Unable to bind the text domain"); @@ -104,7 +160,7 @@ fn main() -> Result<()> { } let provider = gtk::CssProvider::new(); - provider.load_from_resource(&format!("{}/style.css", RESOURCES_BASE_PATH)); + provider.load_from_resource(&format!("{RESOURCES_BASE_PATH}/style.css")); if let Some(display) = gdk::Display::default() { gtk::style_context_add_provider_for_display( &display, diff --git a/src/openxr_prober.rs b/src/openxr_prober.rs index c013959..de756e3 100644 --- a/src/openxr_prober.rs +++ b/src/openxr_prober.rs @@ -17,7 +17,7 @@ pub fn is_openxr_ready() -> bool { let Ok(xr_instance) = entry.create_instance( &xr::ApplicationInfo { - application_name: &format!("{}-openxr-prober", CMD_NAME), + application_name: &format!("{CMD_NAME}-openxr-prober"), application_version: 0, engine_name: CMD_NAME, engine_version: 0, diff --git a/src/profile.rs b/src/profile.rs index ad6b170..2666291 100644 --- a/src/profile.rs +++ b/src/profile.rs @@ -2,7 +2,8 @@ use crate::{ depcheck::{ basalt_deps::get_missing_basalt_deps, libsurvive_deps::get_missing_libsurvive_deps, mercury_deps::get_missing_mercury_deps, monado_deps::get_missing_monado_deps, - openhmd_deps::get_missing_openhmd_deps, wivrn_deps::get_missing_wivrn_deps, Dependency, + openhmd_deps::get_missing_openhmd_deps, wivrn_deps::get_missing_wivrn_deps, + xrizer_deps::get_missing_xrizer_deps, Dependency, }, file_builders::active_runtime_json::ActiveRuntime, paths::{get_data_dir, BWRAP_SYSTEM_PREFIX, SYSTEM_PREFIX}, @@ -14,7 +15,7 @@ use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, fmt::Display, - fs::File, + fs::{remove_dir_all, File}, io::BufReader, path::{Path, PathBuf}, slice::Iter, @@ -265,6 +266,7 @@ pub enum OvrCompatibilityModuleType { #[default] Opencomposite, Xrizer, + Vapor, } impl Display for OvrCompatibilityModuleType { @@ -272,13 +274,23 @@ impl Display for OvrCompatibilityModuleType { f.write_str(match self { Self::Opencomposite => "OpenComposite", Self::Xrizer => "xrizer", + Self::Vapor => "VapoR", }) } } impl OvrCompatibilityModuleType { pub fn iter() -> Iter<'static, Self> { - [Self::Opencomposite, Self::Xrizer].iter() + [Self::Opencomposite, Self::Xrizer, Self::Vapor].iter() + } + + pub fn get_missing_deps(&self) -> Vec { + match self { + OvrCompatibilityModuleType::Xrizer => get_missing_xrizer_deps(), + OvrCompatibilityModuleType::Opencomposite | OvrCompatibilityModuleType::Vapor => { + Vec::default() + } + } } } @@ -289,6 +301,7 @@ impl FromStr for OvrCompatibilityModuleType { match s.to_lowercase().trim() { "opencomposite" => Ok(Self::Opencomposite), "xrizer" => Ok(Self::Xrizer), + "vapor" => Ok(Self::Vapor), _ => Err(format!("no match for ovr compatibility module `{s}`")), } } @@ -299,7 +312,8 @@ impl From for OvrCompatibilityModuleType { match value { 0 => Self::Opencomposite, 1 => Self::Xrizer, - _ => panic!("OvrCompatibilityModuleType index out of bounds"), + 2 => Self::Vapor, + _ => panic!("OvrCompatibilityModuleType index out of bounds"), } } } @@ -328,6 +342,7 @@ impl ProfileOvrCompatibilityModule { pub fn runtime_dir(&self) -> PathBuf { match self.mod_type { OvrCompatibilityModuleType::Opencomposite => self.path.join("build"), + OvrCompatibilityModuleType::Vapor => self.path.join("build/install_pfx/lib/VapoR"), OvrCompatibilityModuleType::Xrizer => self.path.join("target/release"), } } @@ -444,6 +459,29 @@ impl Profile { get_data_dir().join("prefixes").join(uuid) } + /// deletes files and folders associated to this profile (mostly repo clones) + pub fn delete_files(&self) -> Vec> { + [ + Some(&self.xrservice_path), + Some(&self.ovr_comp.path), + self.features.libsurvive.path.as_ref(), + self.features.basalt.path.as_ref(), + self.features.openhmd.path.as_ref(), + ] + .iter() + .map(|dir| match dir { + Some(dir) => { + if dir.try_exists().unwrap_or_default() { + remove_dir_all(dir) + } else { + Ok(()) + } + } + None => Ok(()), + }) + .collect() + } + pub fn xr_runtime_json_env_var(&self) -> String { format!( "XR_RUNTIME_JSON=\"{prefix}/share/openxr/1/openxr_{runtime}.json\"", @@ -708,7 +746,7 @@ impl Profile { if self.features.mercury_enabled { missing_deps.extend(get_missing_mercury_deps()); } - // no listed deps for opencomp + missing_deps.extend(self.ovr_comp.mod_type.get_missing_deps()); } missing_deps.sort_unstable(); missing_deps.dedup(); // dedup only works if sorted, hence the above diff --git a/src/profiles/lighthouse.rs b/src/profiles/lighthouse.rs index 97f3a1f..9ba0af8 100644 --- a/src/profiles/lighthouse.rs +++ b/src/profiles/lighthouse.rs @@ -21,7 +21,7 @@ pub fn lighthouse_profile() -> Profile { environment.insert("LD_LIBRARY_PATH".into(), prepare_ld_library_path(&prefix)); Profile { uuid: "lighthouse-default".into(), - name: format!("Lighthouse Driver - {name} Default", name = APP_NAME), + name: format!("Lighthouse Driver - {APP_NAME} Default"), xrservice_path: data_monado_path(), xrservice_type: XRServiceType::Monado, ovr_comp: ProfileOvrCompatibilityModule { diff --git a/src/profiles/openhmd.rs b/src/profiles/openhmd.rs index 8709725..71ec6f7 100644 --- a/src/profiles/openhmd.rs +++ b/src/profiles/openhmd.rs @@ -21,7 +21,7 @@ pub fn openhmd_profile() -> Profile { environment.insert("LD_LIBRARY_PATH".into(), prepare_ld_library_path(&prefix)); Profile { uuid: "openhmd-default".into(), - name: format!("OpenHMD - {name} Default", name = APP_NAME), + name: format!("OpenHMD - {APP_NAME} Default"), xrservice_path: data_monado_path(), xrservice_type: XRServiceType::Monado, ovr_comp: ProfileOvrCompatibilityModule { diff --git a/src/profiles/simulated.rs b/src/profiles/simulated.rs index a39a66b..c5af2ec 100644 --- a/src/profiles/simulated.rs +++ b/src/profiles/simulated.rs @@ -22,7 +22,7 @@ pub fn simulated_profile() -> Profile { ); Profile { uuid: "simulated-default".into(), - name: format!("Simulated Driver - {name} Default", name = APP_NAME), + name: format!("Simulated Driver - {APP_NAME} Default"), xrservice_path: data_monado_path(), xrservice_type: XRServiceType::Monado, ovr_comp: ProfileOvrCompatibilityModule { diff --git a/src/profiles/survive.rs b/src/profiles/survive.rs index d3e8ff6..6349b77 100644 --- a/src/profiles/survive.rs +++ b/src/profiles/survive.rs @@ -23,7 +23,7 @@ pub fn survive_profile() -> Profile { environment.insert("LD_LIBRARY_PATH".into(), prepare_ld_library_path(&prefix)); Profile { uuid: "survive-default".into(), - name: format!("Survive - {name} Default", name = APP_NAME), + name: format!("Survive - {APP_NAME} Default"), xrservice_path: data_monado_path(), xrservice_type: XRServiceType::Monado, ovr_comp: ProfileOvrCompatibilityModule { diff --git a/src/profiles/wivrn.rs b/src/profiles/wivrn.rs index 980e1ed..b68e15d 100644 --- a/src/profiles/wivrn.rs +++ b/src/profiles/wivrn.rs @@ -19,7 +19,7 @@ pub fn wivrn_profile() -> Profile { environment.insert("U_PACING_APP_USE_MIN_FRAME_PERIOD".into(), "1".into()); Profile { uuid: "wivrn-default".into(), - name: format!("WiVRn - {name} Default", name = APP_NAME), + name: format!("WiVRn - {APP_NAME} Default"), xrservice_path: data_wivrn_path(), xrservice_type: XRServiceType::Wivrn, ovr_comp: ProfileOvrCompatibilityModule { diff --git a/src/profiles/wmr.rs b/src/profiles/wmr.rs index c332f6a..ae99d62 100644 --- a/src/profiles/wmr.rs +++ b/src/profiles/wmr.rs @@ -21,7 +21,7 @@ pub fn wmr_profile() -> Profile { environment.insert("LD_LIBRARY_PATH".into(), prepare_ld_library_path(&prefix)); Profile { uuid: "wmr-default".into(), - name: format!("WMR - {name} Default", name = APP_NAME), + name: format!("WMR - {APP_NAME} Default"), xrservice_path: data_monado_path(), xrservice_type: XRServiceType::Monado, ovr_comp: ProfileOvrCompatibilityModule { diff --git a/src/steam_linux_runtime_injector.rs b/src/steam_linux_runtime_injector.rs index 97f50c5..6e08b5f 100644 --- a/src/steam_linux_runtime_injector.rs +++ b/src/steam_linux_runtime_injector.rs @@ -2,7 +2,7 @@ use crate::{ paths::get_backup_dir, profile::Profile, util::{ - file_utils::{copy_file, get_writer}, + file_utils::{copy_file, get_writer, mark_as_executable}, steam_library_folder::SteamLibraryFolder, }, }; @@ -15,45 +15,85 @@ use std::{ }; use tracing::error; -pub const PRESSURE_VESSEL_STEAM_APPID: u32 = 1628350; +pub const SNIPER_RUNTIME_STEAM_APPID: u32 = 1628350; +pub const SOLDIER_RUNTIME_STEAM_APPID: u32 = 1391110; -fn get_runtime_entrypoint_path() -> Option { +fn get_sniper_runtime_entrypoint_path() -> Option { match SteamLibraryFolder::get_folders() { Ok(libraryfolders) => libraryfolders .iter() - .find(|(_, folder)| folder.apps.contains_key(&PRESSURE_VESSEL_STEAM_APPID)) + .find(|(_, folder)| folder.apps.contains_key(&SNIPER_RUNTIME_STEAM_APPID)) .map(|(_, folder)| { PathBuf::from(&folder.path) .join("steamapps/common/SteamLinuxRuntime_sniper/_v2-entry-point") }), Err(e) => { - error!("unable to get runtime entrypoint path: {e}"); + error!("unable to get sniper runtime entrypoint path: {e}"); + None + } + } +} + +fn get_soldier_runtime_entrypoint_path() -> Option { + match SteamLibraryFolder::get_folders() { + Ok(libraryfolders) => libraryfolders + .iter() + .find(|(_, folder)| folder.apps.contains_key(&SOLDIER_RUNTIME_STEAM_APPID)) + .map(|(_, folder)| { + PathBuf::from(&folder.path) + .join("steamapps/common/SteamLinuxRuntime_soldier/_v2-entry-point") + }), + Err(e) => { + error!("unable to get soldier runtime entrypoint path: {e}"); None } } } lazy_static! { - static ref STEAM_RUNTIME_ENTRYPOINT_PATH: Option = get_runtime_entrypoint_path(); + static ref STEAM_SNIPER_RUNTIME_ENTRYPOINT_PATH: Option = + get_sniper_runtime_entrypoint_path(); + static ref STEAM_SOLDIER_RUNTIME_ENTRYPOINT_PATH: Option = + get_soldier_runtime_entrypoint_path(); } -fn get_backup_runtime_entrypoint_location() -> PathBuf { +fn get_backup_sniper_runtime_entrypoint_location() -> PathBuf { get_backup_dir().join("_v2-entry-point.bak") } -fn backup_runtime_entrypoint(path: &Path) { - copy_file(path, &get_backup_runtime_entrypoint_location()); +fn get_backup_soldier_runtime_entrypoint_location() -> PathBuf { + get_backup_dir().join("_v2-entry-point.soldier.bak") } -pub fn restore_runtime_entrypoint() { - if let Some(path) = STEAM_RUNTIME_ENTRYPOINT_PATH.as_ref() { - let backup = get_backup_runtime_entrypoint_location(); +fn backup_sniper_runtime_entrypoint(path: &Path) { + copy_file(path, &get_backup_sniper_runtime_entrypoint_location()); +} + +fn backup_soldier_runtime_entrypoint(path: &Path) { + copy_file(path, &get_backup_soldier_runtime_entrypoint_location()); +} + +pub fn restore_sniper_runtime_entrypoint() { + if let Some(path) = STEAM_SNIPER_RUNTIME_ENTRYPOINT_PATH.as_ref() { + let backup = get_backup_sniper_runtime_entrypoint_location(); if Path::new(&backup).is_file() { copy_file(&backup, path); + let _ = mark_as_executable(path); } } } +pub fn restore_soldier_runtime_entrypoint() { + if let Some(path) = STEAM_SOLDIER_RUNTIME_ENTRYPOINT_PATH.as_ref() { + let backup = get_backup_soldier_runtime_entrypoint_location(); + if Path::new(&backup).is_file() { + copy_file(&backup, path); + let _ = mark_as_executable(path); + } + } +} + +/// this implementation is identical for both sniper and soldier runtimes fn append_to_runtime_entrypoint(data: &str, path: &Path) -> anyhow::Result<()> { let existing = read_to_string(path)?; let new = existing.replace( @@ -65,10 +105,12 @@ fn append_to_runtime_entrypoint(data: &str, path: &Path) -> anyhow::Result<()> { Ok(()) } -pub fn set_runtime_entrypoint_launch_opts_from_profile(profile: &Profile) -> anyhow::Result<()> { - restore_runtime_entrypoint(); - if let Some(dest) = STEAM_RUNTIME_ENTRYPOINT_PATH.as_ref() { - backup_runtime_entrypoint(dest); +pub fn set_sniper_runtime_entrypoint_launch_opts_from_profile( + profile: &Profile, +) -> anyhow::Result<()> { + restore_sniper_runtime_entrypoint(); + if let Some(dest) = STEAM_SNIPER_RUNTIME_ENTRYPOINT_PATH.as_ref() { + backup_sniper_runtime_entrypoint(dest); append_to_runtime_entrypoint( &profile .get_env_vars() @@ -78,8 +120,31 @@ pub fn set_runtime_entrypoint_launch_opts_from_profile(profile: &Profile) -> any .join("\n"), dest, )?; + mark_as_executable(dest)?; return Ok(()); } - bail!("Could not find valid runtime entrypoint"); + bail!("Could not find valid sniper runtime entrypoint"); +} + +pub fn set_soldier_runtime_entrypoint_launch_opts_from_profile( + profile: &Profile, +) -> anyhow::Result<()> { + restore_soldier_runtime_entrypoint(); + if let Some(dest) = STEAM_SOLDIER_RUNTIME_ENTRYPOINT_PATH.as_ref() { + backup_soldier_runtime_entrypoint(dest); + append_to_runtime_entrypoint( + &profile + .get_env_vars() + .iter() + .map(|ev| "export ".to_string() + ev) + .collect::>() + .join("\n"), + dest, + )?; + mark_as_executable(dest)?; + + return Ok(()); + } + bail!("Could not find valid soldier runtime entrypoint"); } diff --git a/src/ui/about_dialog.rs b/src/ui/about_dialog.rs index 8f4235e..0e28534 100644 --- a/src/ui/about_dialog.rs +++ b/src/ui/about_dialog.rs @@ -33,7 +33,7 @@ pub fn create_about_dialog() -> adw::AboutDialog { const UNKNOWN: &str = "UNKNOWN"; pub fn populate_debug_info(dialog: &adw::AboutDialog, vkinfo: Option<&VulkanInfo>) { - if dialog.debug_info().len() > 0 { + if !dialog.debug_info().is_empty() { return; } let distro_family = LinuxDistro::get(); @@ -70,7 +70,7 @@ pub fn populate_debug_info(dialog: &adw::AboutDialog, vkinfo: Option<&VulkanInfo .and_then(|s| { s.split("\n") .find(|line| line.starts_with("model name")) - .map(|line| line.split(':').last().map(|s| s.trim().to_string())) + .map(|line| line.split(':').next_back().map(|s| s.trim().to_string())) }) .flatten() .unwrap_or(UNKNOWN.into()) @@ -81,12 +81,6 @@ pub fn populate_debug_info(dialog: &adw::AboutDialog, vkinfo: Option<&VulkanInfo .map(|i| i.gpu_names.join(", ")) .unwrap_or(UNKNOWN.into()) ), - format!( - "Monado Vulkan Layers: {}", - vkinfo - .map(|i| i.has_monado_vulkan_layers.to_string()) - .unwrap_or(UNKNOWN.into()) - ), format!("Detected XR Devices: {}", { let devs = PhysicalXRDevice::from_usb(); if devs.is_empty() { diff --git a/src/ui/app.rs b/src/ui/app.rs index 98510a3..a9b9265 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -20,7 +20,8 @@ use crate::{ build_basalt::get_build_basalt_jobs, build_libsurvive::get_build_libsurvive_jobs, build_mercury::get_build_mercury_jobs, build_monado::get_build_monado_jobs, build_opencomposite::get_build_opencomposite_jobs, build_openhmd::get_build_openhmd_jobs, - build_wivrn::get_build_wivrn_jobs, build_xrizer::get_build_xrizer_jobs, + build_vapor::get_build_vapor_jobs, build_wivrn::get_build_wivrn_jobs, + build_xrizer::get_build_xrizer_jobs, }, config::{Config, PluginConfig}, constants::APP_NAME, @@ -40,8 +41,11 @@ use crate::{ profile::{OvrCompatibilityModuleType, Profile, XRServiceType}, stateless_action, steam_linux_runtime_injector::{ - restore_runtime_entrypoint, set_runtime_entrypoint_launch_opts_from_profile, + restore_sniper_runtime_entrypoint, restore_soldier_runtime_entrypoint, + set_sniper_runtime_entrypoint_launch_opts_from_profile, + set_soldier_runtime_entrypoint_launch_opts_from_profile, }, + termcolor::TermColor, util::file_utils::{ setcap_cap_sys_nice_eip, setcap_cap_sys_nice_eip_cmd, verify_cap_sys_nice_eip, }, @@ -50,6 +54,7 @@ use crate::{ xr_devices::XRDevice, }; use adw::{prelude::*, ResponseAppearance}; +use delicious_adwaita::{theme::Theme, ThemeEngine}; use gtk::glib::{self, clone}; use notify_rust::NotificationHandle; use relm4::{ @@ -95,6 +100,8 @@ pub struct App { inhibit_fail_notif: Option, pluginstore: Option>, + + theme_engine: ThemeEngine, } #[derive(Debug)] @@ -112,7 +119,8 @@ pub enum Msg { StartWithDebug, RestartXRService, ProfileSelected(Profile), - DeleteProfile, + /// bool param: delete files + DeleteProfile(bool), SaveProfile(Profile), RunSetCap, OpenLibsurviveSetup, @@ -128,6 +136,8 @@ pub enum Msg { WivrnCheckPairMode, OpenPluginStore, UpdateConfigPlugins(HashMap), + ShowThemeManager, + SaveThemeConfig, NoOp, } @@ -226,13 +236,17 @@ impl App { ); worker.start(); self.xrservice_worker = Some(worker); + let set_sniper_launch_opts_res = + set_sniper_runtime_entrypoint_launch_opts_from_profile(&prof); + let set_soldier_launch_opts_res = + set_soldier_runtime_entrypoint_launch_opts_from_profile(&prof); self.main_view .sender() .emit(MainViewMsg::XRServiceActiveChanged( true, Some(self.get_selected_profile()), // show launch opts only if setting the runtime entrypoint fails - set_runtime_entrypoint_launch_opts_from_profile(&prof).is_err(), + set_sniper_launch_opts_res.is_err() || set_soldier_launch_opts_res.is_err(), )); self.debug_view .sender() @@ -296,7 +310,8 @@ impl App { } pub fn restore_openxr_openvr_files(&self) { - restore_runtime_entrypoint(); + restore_sniper_runtime_entrypoint(); + restore_soldier_runtime_entrypoint(); if let Err(e) = remove_current_active_runtime() { alert( "Could not remove profile active runtime", @@ -365,7 +380,7 @@ impl AsyncComponent for App { set_content: Some(&adw::NavigationPage::new(model.debug_view.widget(), "Debug View")), set_show_content: false, set_collapsed: !model.config.debug_view_enabled, - } + }, }, connect_close_request[sender] => move |win| { sender.input(Msg::SaveWinSize(win.width(), win.height())); @@ -389,6 +404,27 @@ impl AsyncComponent for App { ) { match message { Msg::NoOp => {} + Msg::ShowThemeManager => { + let dialog = self + .theme_engine + .theme_chooser_dialog(Theme::default_themes().as_ref()); + dialog.set_content_height(2000); + dialog.present(Some(&self.app_win)); + dialog.connect_closed(clone!( + #[strong] + sender, + move |_| { + sender.input(Msg::SaveThemeConfig); + } + )); + } + Msg::SaveThemeConfig => { + let name = self.theme_engine.current_theme_name(); + if self.config.theme_name != name { + self.config.theme_name = name; + self.config.save(); + } + } Msg::OnServiceLog(rows) => { if !rows.is_empty() { self.debug_view @@ -519,7 +555,7 @@ impl AsyncComponent for App { jobs.extend(get_build_basalt_jobs(&profile, clean_build)); } if profile.features.mercury_enabled { - jobs.extend(get_build_mercury_jobs(&profile)); + jobs.extend(get_build_mercury_jobs()); } jobs.extend(match profile.xrservice_type { XRServiceType::Monado => get_build_monado_jobs(&profile, clean_build), @@ -533,6 +569,9 @@ impl AsyncComponent for App { OvrCompatibilityModuleType::Xrizer => { get_build_xrizer_jobs(&profile, clean_build) } + OvrCompatibilityModuleType::Vapor => { + get_build_vapor_jobs(&profile, clean_build) + } }); let missing_deps = profile.missing_dependencies(); if !(self.skip_depcheck || profile.skip_dependency_check || missing_deps.is_empty()) @@ -622,6 +661,10 @@ impl AsyncComponent for App { if dep_pkexec().check() { self.setcap_confirm_dialog.present(Some(&self.app_win)); } else { + self.build_window + .sender() + .emit(BuildWindowMsg::UpdateContent(vec![TermColor::Red + .colorize("pkexec not found, cannot set capabilities\n")])); alert_w_widget( "pkexec not found", Some(&format!( @@ -644,7 +687,7 @@ impl AsyncComponent for App { self.build_window .sender() .emit(BuildWindowMsg::UpdateBuildStatus(BuildStatus::Error( - format!("Exit status {}", errcode), + format!("Exit status {errcode}"), ))); } }; @@ -654,9 +697,16 @@ impl AsyncComponent for App { w.stop(); } } - Msg::DeleteProfile => { + Msg::DeleteProfile(delete_files) => { let todel = self.get_selected_profile(); if todel.editable { + if delete_files { + for res in todel.delete_files() { + if let Err(e) = res { + error!("Error deleting profile directory: {e}"); + } + } + } self.config.user_profiles.retain(|p| p.uuid != todel.uuid); self.config.save(); self.profiles = self.config.profiles(); @@ -696,6 +746,7 @@ impl AsyncComponent for App { } Msg::RunSetCap => { if !dep_pkexec().check() { + // there's a precheck ahead of this, this should likely never happen error!("pkexec not found, skipping setcap"); } else { let profile = self.get_selected_profile(); @@ -713,8 +764,26 @@ impl AsyncComponent for App { if let Err(e) = setcap_cap_sys_nice_eip(&profile).await { setcap_failed_dialog(); error!("failed running setcap: {e}"); + self.build_window + .sender() + .emit(BuildWindowMsg::UpdateContent(vec![ + TermColor::Red.colorize("Setting capabilities failed\n") + ])); } else if !verify_cap_sys_nice_eip(&profile).await { setcap_failed_dialog(); + error!("setcap succeeded but capabilities were reset"); + self.build_window + .sender() + .emit(BuildWindowMsg::UpdateContent(vec![TermColor::Red + .colorize( + "Setting capabilities succeeded, but capabilities have been reset\n", + )])); + } else { + self.build_window + .sender() + .emit(BuildWindowMsg::UpdateContent(vec![ + TermColor::Green.colorize("Capabilities set correctly\n") + ])); } } } @@ -962,6 +1031,17 @@ impl AsyncComponent for App { } ) ); + stateless_action!( + actions, + ThemeManagerAction, + clone!( + #[strong] + sender, + move |_| { + sender.input(Msg::ShowThemeManager); + } + ) + ); // this bypasses the macro because I need the underlying gio action // to enable/disable it in update() let configure_wivrn_action = { @@ -998,13 +1078,12 @@ impl AsyncComponent for App { config: config.clone(), selected_profile: selected_profile.clone(), root_win: root.clone().into(), - vkinfo: vkinfo.clone(), }) .forward(sender.input_sender(), |message| match message { MainViewOutMsg::DoStartStopXRService => Msg::DoStartStopXRService, MainViewOutMsg::RestartXRService => Msg::RestartXRService, MainViewOutMsg::ProfileSelected(uuid) => Msg::ProfileSelected(uuid), - MainViewOutMsg::DeleteProfile => Msg::DeleteProfile, + MainViewOutMsg::DeleteProfile(delete_files) => Msg::DeleteProfile(delete_files), MainViewOutMsg::SaveProfile(p) => Msg::SaveProfile(p), MainViewOutMsg::OpenLibsurviveSetup => Msg::OpenLibsurviveSetup, MainViewOutMsg::BuildProfile(clean) => Msg::BuildProfile(clean), @@ -1031,6 +1110,17 @@ impl AsyncComponent for App { .detach(), split_view: None, setcap_confirm_dialog, + theme_engine: ThemeEngine::new_with_theme(&{ + if config.theme_name == "Follow system" { + Theme::default() + } else { + Theme::default_themes() + .into_iter() + .find(|t| t.name == config.theme_name) + .unwrap_or_default() + } + }) + .unwrap(), config, profiles, xrservice_worker: None, @@ -1119,6 +1209,7 @@ new_stateless_action!(pub QuitAction, AppActionGroup, "quit"); new_stateful_action!(pub DebugViewToggleAction, AppActionGroup, "debugviewtoggle", (), bool); new_stateless_action!(pub ConfigureWivrnAction, AppActionGroup, "configurewivrn"); new_stateless_action!(pub PluginStoreAction, AppActionGroup, "store"); +new_stateless_action!(pub ThemeManagerAction, AppActionGroup, "thememanager"); new_stateless_action!(pub DebugOpenDataAction, AppActionGroup, "debugopendata"); new_stateless_action!(pub DebugOpenPrefixAction, AppActionGroup, "debugopenprefix"); diff --git a/src/ui/build_window.rs b/src/ui/build_window.rs index 759af9b..1bc966b 100644 --- a/src/ui/build_window.rs +++ b/src/ui/build_window.rs @@ -1,3 +1,5 @@ +use crate::termcolor::TermColor; + use super::{term_widget::TermWidget, SENDER_IO_ERR_MSG}; use adw::prelude::*; use relm4::prelude::*; @@ -88,43 +90,54 @@ impl SimpleComponent for BuildWindow { gtk::Label { #[track = "model.changed(BuildWindow::build_status())"] set_markup: match &model.build_status { - BuildStatus::Building => "Build in progress...".to_string(), - BuildStatus::Done => "Build done, you can close this window".to_string(), + BuildStatus::Building => String::default(), + BuildStatus::Done => "Build done, you can close this window".into(), BuildStatus::Error(code) => { - format!("Build failed: \"{c}\"", c = code) + format!("Build failed: \"{code}\"") } }.as_str(), + #[track = "model.changed(BuildWindow::build_status())"] + set_visible: match &model.build_status { + BuildStatus::Building => false, + BuildStatus::Done | BuildStatus::Error(_) => true, + }, add_css_class: "title-2", set_wrap: true, set_wrap_mode: gtk::pango::WrapMode::Word, set_justify: gtk::Justification::Center, }, - gtk::Button { - #[track = "model.changed(BuildWindow::build_status())"] - set_visible: matches!(&model.build_status, BuildStatus::Building), - add_css_class: "destructive-action", - add_css_class: "circular", - set_icon_name: "window-close-symbolic", - set_tooltip_text: Some("Cancel build"), - connect_clicked[sender] => move |_| { - sender.output(Self::Output::CancelBuild).expect(SENDER_IO_ERR_MSG); - } - }, }, model.term.container.clone(), }, - add_bottom_bar: bottom_bar = >k::Button { - add_css_class: "pill", + add_bottom_bar: bottom_bar = >k::Box { + set_orientation: gtk::Orientation::Horizontal, set_halign: gtk::Align::Center, - set_label: "Close", - set_margin_all: 12, - #[track = "model.changed(BuildWindow::can_close())"] - set_sensitive: model.can_close, - connect_clicked[win] => move |_| { - - win.close(); + set_hexpand: true, + set_margin_bottom: 24, + set_spacing: 12, + gtk::Button { + add_css_class: "pill", + set_halign: gtk::Align::Center, + set_label: "Close", + #[track = "model.changed(BuildWindow::can_close())"] + set_visible: model.can_close, + connect_clicked[win] => move |_| { + win.close(); + }, }, - } + // this + gtk::Button { + #[track = "model.changed(BuildWindow::build_status())"] + set_visible: matches!(&model.build_status, BuildStatus::Building), + add_css_class: "destructive-action", + add_css_class: "pill", + set_label: "Cancel build", + connect_clicked[sender] => move |_| { + sender.output(Self::Output::CancelBuild).expect(SENDER_IO_ERR_MSG); + } + }, + // ^^^ + }, } } } @@ -153,8 +166,18 @@ impl SimpleComponent for BuildWindow { label.remove_css_class("success"); label.remove_css_class("error"); match status { - BuildStatus::Done => label.add_css_class("success"), - BuildStatus::Error(_) => label.add_css_class("error"), + BuildStatus::Done => { + label.add_css_class("success"); + sender.input(BuildWindowMsg::UpdateContent(vec![ + TermColor::Blue.colorize("Build completed!\n") + ])); + } + BuildStatus::Error(_) => { + label.add_css_class("error"); + sender.input(BuildWindowMsg::UpdateContent(vec![ + TermColor::Blue.colorize("Build failed!\n") + ])); + } _ => {} } if status != BuildStatus::Building { diff --git a/src/ui/main_view.rs b/src/ui/main_view.rs index c81a5a9..8a2c145 100644 --- a/src/ui/main_view.rs +++ b/src/ui/main_view.rs @@ -21,11 +21,11 @@ use crate::{ paths::{get_data_dir, get_home_dir}, profile::{LighthouseDriver, Profile, XRServiceType}, stateless_action, + ui::app::ThemeManagerAction, util::{ file_utils::{get_writer, mount_has_nosuid}, steamvr_utils::chaperone_info_exists, }, - vulkaninfo::VulkanInfo, wivrn_dbus, xr_devices::XRDevice, }; @@ -75,8 +75,6 @@ pub struct MainView { #[tracker::do_not_track] profile_export_action: gtk::gio::SimpleAction, xrservice_ready: bool, - #[tracker::do_not_track] - vkinfo: Option, wivrn_pairing_mode: bool, wivrn_pin: Option, wivrn_supports_pairing: bool, @@ -115,7 +113,8 @@ pub enum MainViewOutMsg { DoStartStopXRService, RestartXRService, ProfileSelected(Profile), - DeleteProfile, + /// bool param: delete files + DeleteProfile(bool), SaveProfile(Profile), OpenLibsurviveSetup, /// params: clean @@ -126,7 +125,6 @@ pub struct MainViewInit { pub config: Config, pub selected_profile: Profile, pub root_win: gtk::Window, - pub vkinfo: Option, } impl MainView { @@ -162,6 +160,7 @@ impl AsyncComponent for MainView { "Configure _WiVRn" => ConfigureWivrnAction, }, section! { + "Change _Theme" => ThemeManagerAction, "_About" => AboutAction, }, }, @@ -461,34 +460,6 @@ impl AsyncComponent for MainView { set_wrap_mode: gtk::pango::WrapMode::Word, } }, - gtk::Box { - set_orientation: gtk::Orientation::Vertical, - set_hexpand: true, - set_vexpand: false, - set_spacing: 12, - add_css_class: "card", - add_css_class: "padded", - set_visible: model - .vkinfo - .as_ref() - .is_some_and( - |i| i.has_nvidia_gpu && !i.has_monado_vulkan_layers - ), - warning_heading(), - gtk::Label { - set_label: concat!( - "An Nvidia GPU has been detected, but it ", - "seems you don't have the Monado Vulkan Layers ", - "installed on your system.\n\nInstall the ", - "Monado Vulkan Layers or your XR session will ", - "crash." - ), - add_css_class: "warning", - set_xalign: 0.0, - set_wrap: true, - set_wrap_mode: gtk::pango::WrapMode::Word, - } - }, gtk::Box { set_orientation: gtk::Orientation::Vertical, set_hexpand: true, @@ -963,6 +934,13 @@ impl AsyncComponent for MainView { let profile_delete_confirm_dialog = adw::AlertDialog::builder() .heading("Are you sure you want to delete this profile?") + .extra_child( + >k::CheckButton::builder() + .label("Delete all files and folders associated with profile") + .halign(gtk::Align::Center) + .hexpand(true) + .build(), + ) .build(); profile_delete_confirm_dialog.add_response("no", "_No"); profile_delete_confirm_dialog.add_response("yes", "_Yes"); @@ -974,10 +952,19 @@ impl AsyncComponent for MainView { clone!( #[strong] sender, - move |_, res| { + move |dialog, res| { + let delete_files_checkbox = dialog + .extra_child() + .and_then(|child| child.downcast::().ok()); + let delete_files = delete_files_checkbox + .as_ref() + .is_some_and(|c| c.is_active()); + if let Some(check) = delete_files_checkbox { + check.set_active(false); + } if res == "yes" { sender - .output(Self::Output::DeleteProfile) + .output(Self::Output::DeleteProfile(delete_files)) .expect("Sender output failed"); } } @@ -1103,7 +1090,6 @@ impl AsyncComponent for MainView { xrservice_ready: false, profile_delete_action, profile_export_action, - vkinfo: init.vkinfo, wivrn_pairing_mode: false, wivrn_supports_pairing: false, wivrn_pin: None, diff --git a/src/ui/plugins/add_custom_plugin_win.rs b/src/ui/plugins/add_custom_plugin_win.rs index c06f0c8..734a4ea 100644 --- a/src/ui/plugins/add_custom_plugin_win.rs +++ b/src/ui/plugins/add_custom_plugin_win.rs @@ -31,6 +31,7 @@ pub enum AddCustomPluginWinMsg { Present, Close, OnNameChange(String), + OnArgsChange(String), OnExecPathChange(Option), Add, } @@ -95,7 +96,11 @@ impl SimpleComponent for AddCustomPluginWin { "", clone!( #[strong] sender, - move |row| sender.input(Self::Input::OnNameChange(row.text().to_string())) + move |row| sender.input( + Self::Input::OnNameChange( + row.text().to_string() + ) + ) ) ), add: &file_row( @@ -105,9 +110,23 @@ impl SimpleComponent for AddCustomPluginWin { Some(model.parent.clone()), clone!( #[strong] sender, - move |path_s| sender.input(Self::Input::OnExecPathChange(path_s)) + move |path_s| sender.input( + Self::Input::OnExecPathChange(path_s) + ) ) - ) + ), + add: &entry_row( + "Plugin Arguments", + "", + clone!( + #[strong] sender, + move |row| sender.input( + Self::Input::OnArgsChange( + row.text().to_string() + ) + ) + ) + ), }, }, }, @@ -139,6 +158,17 @@ impl SimpleComponent for AddCustomPluginWin { self.plugin.name = name; self.set_can_add(self.plugin.validate()); } + Self::Input::OnArgsChange(args) => { + let args = args.trim().to_string(); + self.plugin.args = if args.is_empty() { + None + } else { + // it's fine to have them joined + // since they will ultimately be + // passed as a joined string + Some(vec![args]) + } + } Self::Input::OnExecPathChange(ep) => { self.plugin.exec_path = ep.map(PathBuf::from); self.set_can_add(self.plugin.validate()); diff --git a/src/ui/plugins/mod.rs b/src/ui/plugins/mod.rs index 8d2d792..cb18440 100644 --- a/src/ui/plugins/mod.rs +++ b/src/ui/plugins/mod.rs @@ -165,8 +165,8 @@ impl Plugin { /// each manifest should be json and the link should always point to the latest version const MANIFESTS: [&str;3] = [ "https://github.com/galister/wlx-overlay-s/raw/refs/heads/meta/com.github.galister.wlx-overlay-s.json", - "https://github.com/olekolek1000/wayvr-dashboard/raw/refs/heads/meta/dev.oo8.wayvr_dashboard.json", "https://github.com/StardustXR/telescope/raw/refs/heads/main/envision/org.stardustxr.telescope.json", + "https://github.com/olekolek1000/wayvr-dashboard/raw/refs/heads/meta/dev.oo8.wayvr_dashboard.json", ]; pub async fn refresh_plugins() -> anyhow::Result>> { diff --git a/src/ui/preference_rows.rs b/src/ui/preference_rows.rs index 4502ebf..bace44c 100644 --- a/src/ui/preference_rows.rs +++ b/src/ui/preference_rows.rs @@ -213,7 +213,7 @@ pub fn file_row) + 'static + Clone>( let filedialog = gtk::FileDialog::builder() .modal(true) - .title(format!("Select {}", title)) + .title(format!("Select {title}")) .build(); row.connect_activated(clone!( @@ -255,7 +255,7 @@ pub fn path_row) + 'static + Clone>( let (row, path_label) = filedialog_row_base(title, description, value, cb.clone()); let filedialog = gtk::FileDialog::builder() .modal(true) - .title(format!("Select Path for {}", title)) + .title(format!("Select Path for {title}")) .build(); row.connect_activated(clone!( diff --git a/src/ui/steam_launch_options_box.rs b/src/ui/steam_launch_options_box.rs index 6956bfe..28991b0 100644 --- a/src/ui/steam_launch_options_box.rs +++ b/src/ui/steam_launch_options_box.rs @@ -45,8 +45,7 @@ impl SimpleComponent for SteamLaunchOptionsBox { add_css_class: "dim-label", set_hexpand: true, set_label: format!( - "Set this string in the launch options of Steam games, so that they can pick up the {app} runtime correctly", - app = APP_NAME) + "Set this string in the launch options of Steam games, so that they can pick up the {APP_NAME} runtime correctly") .as_str(), set_xalign: 0.0, set_wrap: true, diff --git a/src/ui/wivrn_wired_start_box.rs b/src/ui/wivrn_wired_start_box.rs index 503bcac..b121c5f 100644 --- a/src/ui/wivrn_wired_start_box.rs +++ b/src/ui/wivrn_wired_start_box.rs @@ -112,7 +112,7 @@ impl AsyncComponent for WivrnWiredStartBox { Self::Input::UpdateSelectedProfile(p) => self.set_selected_profile(p), Self::Input::StartWivrnClient => { if !dep_adb().check() { - alert("ADB is not installed", Some(&format!("Please install ADB on your computer to start the WiVRn client from {}.", APP_NAME)), Some(&self.root_win)); + alert("ADB is not installed", Some(&format!("Please install ADB on your computer to start the WiVRn client from {APP_NAME}.")), Some(&self.root_win)); return; } self.set_start_client_status(StartClientStatus::InProgress); diff --git a/src/util/file_utils.rs b/src/util/file_utils.rs index ff56926..04efb13 100644 --- a/src/util/file_utils.rs +++ b/src/util/file_utils.rs @@ -1,4 +1,4 @@ -use crate::{async_process::async_process, profile::Profile}; +use crate::{async_process::async_process, depcheck::common::dep_getcap_setcap, profile::Profile}; use anyhow::bail; use nix::{ errno::Errno, @@ -79,9 +79,29 @@ pub fn set_file_readonly(path: &Path, readonly: bool) -> anyhow::Result<()> { Ok(fs::set_permissions(path, perms)?) } +pub fn setcap_executable() -> Option { + if dep_getcap_setcap().check() { + Some("setcap".into()) + } else if Path::new("/sbin/setcap").try_exists().unwrap_or_default() { + Some("/sbin/setcap".into()) + } else { + None + } +} + +pub fn getcap_executable() -> Option { + if dep_getcap_setcap().check() { + Some("getcap".into()) + } else if Path::new("/sbin/getcap").try_exists().unwrap_or_default() { + Some("/sbin/getcap".into()) + } else { + None + } +} + pub fn setcap_cap_sys_nice_eip_cmd(profile: &Profile) -> Vec { vec![ - "setcap".into(), + setcap_executable().unwrap_or("setcap".into()), "CAP_SYS_NICE=eip".into(), profile .prefix @@ -93,24 +113,29 @@ pub fn setcap_cap_sys_nice_eip_cmd(profile: &Profile) -> Vec { pub async fn verify_cap_sys_nice_eip(profile: &Profile) -> bool { let xrservice_binary = profile.xrservice_binary().to_string_lossy().to_string(); - match async_process("getcap", Some(&[&xrservice_binary]), None).await { - Err(e) => { - error!("failed to run `getcap {xrservice_binary}`: {e:?}"); - false - } - Ok(out) => { - debug!("getcap {xrservice_binary} stdout: {}", out.stdout); - debug!("getcap {xrservice_binary} stderr: {}", out.stderr); - if out.exit_code != 0 { - error!( - "command `getcap {xrservice_binary}` failed with status code {}", - out.exit_code - ); + if let Some(getcap_exec) = getcap_executable() { + match async_process(&getcap_exec, Some(&[&xrservice_binary]), None).await { + Err(e) => { + error!("failed to run `getcap {xrservice_binary}`: {e:?}"); false - } else { - out.stdout.to_lowercase().contains("cap_sys_nice=eip") + } + Ok(out) => { + debug!("getcap {xrservice_binary} stdout: {}", out.stdout); + debug!("getcap {xrservice_binary} stderr: {}", out.stderr); + if out.exit_code != 0 { + error!( + "command `getcap {xrservice_binary}` failed with status code {}", + out.exit_code + ); + false + } else { + out.stdout.to_lowercase().contains("cap_sys_nice=eip") + } } } + } else { + error!("getcap executable does not exist"); + false } } diff --git a/src/vulkaninfo.rs b/src/vulkaninfo.rs index a8f8110..6d76a92 100644 --- a/src/vulkaninfo.rs +++ b/src/vulkaninfo.rs @@ -5,12 +5,10 @@ use ash::{ #[derive(Debug, Clone)] pub struct VulkanInfo { - pub has_nvidia_gpu: bool, - pub has_monado_vulkan_layers: bool, pub gpu_names: Vec, } -const NVIDIA_VENDOR_ID: u32 = 0x10de; +// const NVIDIA_VENDOR_ID: u32 = 0x10de; impl VulkanInfo { /// # Safety @@ -25,40 +23,19 @@ impl VulkanInfo { None, ) }?; - let mut has_nvidia_gpu = false; - let mut has_monado_vulkan_layers = false; let gpu_names = unsafe { instance.enumerate_physical_devices() }? .into_iter() .filter_map(|d| { - let props = unsafe { instance.get_physical_device_properties(d) }; - if props.vendor_id == NVIDIA_VENDOR_ID { - has_nvidia_gpu = true; - } - if !has_monado_vulkan_layers { - has_monado_vulkan_layers = - unsafe { instance.enumerate_device_layer_properties(d) } - .ok() - .map(|layerprops| { - layerprops.iter().any(|lp| { - lp.layer_name_as_c_str().is_ok_and(|name| { - name.to_string_lossy() - == "VK_LAYER_MND_enable_timeline_semaphore" - }) - }) - }) - == Some(true); - } - props - .device_name_as_c_str() - .ok() - .map(|cs| cs.to_string_lossy().to_string()) + Some( + unsafe { instance.get_physical_device_properties(d) } + .device_name_as_c_str() + .ok()? + .to_string_lossy() + .to_string(), + ) }) .collect(); unsafe { instance.destroy_instance(None) }; - Ok(Self { - gpu_names, - has_nvidia_gpu, - has_monado_vulkan_layers, - }) + Ok(Self { gpu_names }) } } diff --git a/test/files/wivrn_config2.json b/test/files/wivrn_config2.json index 84676be..7bd87ec 100644 --- a/test/files/wivrn_config2.json +++ b/test/files/wivrn_config2.json @@ -12,5 +12,5 @@ } ], "application": ["foobar", "baz"], - "tcp_only": true + "tcp-only": true }