diff --git a/src/ui/main_view.rs b/src/ui/main_view.rs index d4ab156..e74a262 100644 --- a/src/ui/main_view.rs +++ b/src/ui/main_view.rs @@ -11,7 +11,7 @@ use super::{ profile_editor::{ProfileEditor, ProfileEditorInit, ProfileEditorMsg, ProfileEditorOutMsg}, steam_launch_options_box::{SteamLaunchOptionsBox, SteamLaunchOptionsBoxMsg}, steamvr_calibration_box::{SteamVrCalibrationBox, SteamVrCalibrationBoxMsg}, - util::{limit_dropdown_width, warning_heading}, + util::limit_dropdown_width, wivrn_wired_start_box::{WivrnWiredStartBox, WivrnWiredStartBoxInit, WivrnWiredStartBoxMsg}, }; use crate::{ @@ -21,10 +21,10 @@ use crate::{ paths::{get_data_dir, get_home_dir}, profile::{LighthouseDriver, Profile, XRServiceType}, stateless_action, - ui::app::ThemeManagerAction, + ui::{app::ThemeManagerAction, util::warn_card}, util::{ file_utils::{get_writer, mount_has_nosuid}, - steamvr_utils::chaperone_info_exists, + steam_library_folder::chaperone_info_exists, }, wivrn_dbus, xr_devices::XRDevice, @@ -399,13 +399,13 @@ impl AsyncComponent for MainView { .expect(SENDER_IO_ERR_MSG); }, }, - gtk::Box { - set_orientation: gtk::Orientation::Vertical, - set_hexpand: true, - set_vexpand: false, - set_spacing: 12, - add_css_class: "card", - add_css_class: "padded", + warn_card(None, concat!( + "Your current prefix is inside a partition ", + "mounted with the nosuid option.\nThis will prevent ", + "the XR runtime from acquiring certain privileges ", + "and will cause noticeable stutter when running XR ", + "applications." + )) -> gtk::Box { #[track = "model.changed(Self::selected_profile())"] set_visible: match mount_has_nosuid(&model.selected_profile.prefix) { Ok(b) => b, @@ -416,93 +416,35 @@ impl AsyncComponent for MainView { false }, }, - warning_heading(), - gtk::Label { - set_label: concat!( - "Your current prefix is inside a partition ", - "mounted with the nosuid option.\nThis will prevent ", - "the XR runtime from acquiring certain privileges ", - "and will cause noticeable stutter when running XR ", - "applications." - ), - 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, - set_vexpand: false, - set_spacing: 12, - add_css_class: "card", - add_css_class: "padded", + warn_card(None, &format!( + "Pkexec wasn't found on your system.\nThis will prevent the XR runtime from acquiring certain priviledges and will cause noticeable stutter when running XR applications.\nYou can fix this by installing the following package on your system: {}", dep_pkexec().package_name() + )) -> gtk::Box { #[track = "model.changed(Self::selected_profile())"] set_visible: !dep_pkexec().check(), - warning_heading(), - gtk::Label { - set_label: &format!( - "Pkexec wasn't found on your system.\nThis will prevent the XR runtime from acquiring certain priviledges and will cause noticeable stutter when running XR applications.\nYou can fix this by installing the following package on your system: {}", dep_pkexec().package_name() - ), - set_use_markup: true, - 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, - set_vexpand: false, - set_spacing: 12, - add_css_class: "card", - add_css_class: "padded", + warn_card(None, concat!( + "SteamVR room configuration not found.\n", + "To use the SteamVR lighthouse driver, you ", + "will need to run SteamVR Quick Calibration.", + )) -> gtk::Box { #[track = "model.changed(Self::selected_profile())"] set_visible: model.selected_profile.lighthouse_driver == LighthouseDriver::SteamVR && !chaperone_info_exists(), - warning_heading(), - gtk::Label { - set_label: concat!( - "SteamVR room configuration not found.\n", - "To use the SteamVR lighthouse driver, you ", - "will need to run SteamVR Quick Calibration.", - ), - 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, - set_vexpand: false, - set_spacing: 12, - add_css_class: "card", - add_css_class: "padded", + warn_card(None, concat!( + "Your AMD GPU Power Profile is not set to VR. ", + "This will cause noticeable stutter when running XR ", + "applications.\n\n", + "You can activate the VR Power Profile using ", + "GPU overclocking utilities such as ", + "CoreCtrl." + )) -> gtk::Box { #[track = "model.changed(Self::selected_profile())"] set_visible: match get_amd_gpu_power_profile() { None => false, Some(GpuPowerProfile::VR) => false, Some(_) => true, }, - warning_heading(), - gtk::Label { - set_use_markup: true, - set_markup: concat!( - "Your AMD GPU Power Profile is not set to VR. ", - "This will cause noticeable stutter when running XR ", - "applications.\n\n", - "You can activate the VR Power Profile using ", - "GPU overclocking utilities such as ", - "CoreCtrl." - ), - add_css_class: "warning", - set_xalign: 0.0, - set_wrap: true, - set_wrap_mode: gtk::pango::WrapMode::Word, - }, gtk::Button { set_halign: gtk::Align::Start, set_label: "Refresh", diff --git a/src/ui/util.rs b/src/ui/util.rs index aa6c95f..46a8a9a 100644 --- a/src/ui/util.rs +++ b/src/ui/util.rs @@ -22,29 +22,6 @@ pub fn limit_dropdown_width(dd: >k::DropDown) { } } -pub fn warning_heading() -> gtk::Box { - let b = gtk::Box::builder() - .orientation(gtk::Orientation::Horizontal) - .spacing(12) - .hexpand(true) - .build(); - - b.append( - >k::Image::builder() - .css_classes(["warning"]) - .icon_name("dialog-warning-symbolic") - .build(), - ); - b.append( - >k::Label::builder() - .css_classes(["warning", "heading"]) - .label("Warning") - .build(), - ); - - b -} - pub fn open_with_default_handler(uri: &str) { if let Err(e) = gio::AppInfo::launch_default_for_uri(uri, gio::AppLaunchContext::NONE) { error!("opening uri {uri}: {e}") @@ -118,3 +95,96 @@ pub fn copiable_code_snippet(code: &str) -> gtk::Widget { container.append(&btn); container.upcast() } + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum InfoCardLevel { + #[default] + Info, + Warn, + // Err, +} + +impl InfoCardLevel { + fn css_class(&self) -> Option<&'static str> { + match self { + Self::Info => None, + Self::Warn => Some("warning"), + // Self::Err => Some("error"), + } + } + + fn image(&self) -> gtk::Image { + let i = gtk::Image::builder() + .icon_name(match self { + Self::Info => "dialog-information-symbolic", + Self::Warn => "dialog-warning-symbolic", + // Self::Err => "dialog-error-symbolic", + }) + .build(); + if let Some(cls) = self.css_class() { + i.add_css_class(cls); + } + i + } + + fn default_label(&self) -> &'static str { + match self { + InfoCardLevel::Info => "Info", + InfoCardLevel::Warn => "Warning", + // InfoCardLevel::Err => "Error", + } + } + + pub fn heading(&self, title: Option<&str>) -> gtk::Box { + let b = gtk::Box::builder() + .orientation(gtk::Orientation::Horizontal) + .spacing(12) + .hexpand(true) + .build(); + + b.append(&self.image()); + let title = title.unwrap_or_else(|| self.default_label()); + let label = gtk::Label::builder() + .label(title) + .css_classes(["heading"]) + .build(); + if let Some(cls) = self.css_class() { + label.add_css_class(cls); + } + b.append(&label); + + b + } +} + +pub fn info_card(level: InfoCardLevel, title: Option<&str>, text: &str) -> gtk::Box { + let b = gtk::Box::builder() + .orientation(gtk::Orientation::Vertical) + .hexpand(true) + .vexpand(false) + .spacing(12) + .css_classes(["padded", "card"]) + .build(); + + b.append(&level.heading(title)); + + let text_label = gtk::Label::builder() + .label(text) + .use_markup(true) + .xalign(0.0) + .wrap(true) + .wrap_mode(gtk::pango::WrapMode::Word) + .build(); + + if let Some(cls) = level.css_class() { + text_label.add_css_class(cls); + } + + b.append(&text_label); + + b +} + +pub fn warn_card(title: Option<&str>, text: &str) -> gtk::Box { + info_card(InfoCardLevel::Warn, title, text) +} diff --git a/src/util/mod.rs b/src/util/mod.rs index c61c579..5136072 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -2,4 +2,3 @@ pub mod file_utils; pub mod git_repos; pub mod hash; pub mod steam_library_folder; -pub mod steamvr_utils; diff --git a/src/util/steam_library_folder.rs b/src/util/steam_library_folder.rs index e9a1b51..baa2691 100644 --- a/src/util/steam_library_folder.rs +++ b/src/util/steam_library_folder.rs @@ -13,36 +13,32 @@ pub struct SteamLibraryFolder { pub apps: HashMap, } -pub fn get_steam_main_dir_path() -> anyhow::Result { - if let Ok(steam_root) = get_home_dir().join(".steam/root").canonicalize() - && steam_root.is_dir() - { - Ok(steam_root) - } else if let Ok(steam_root) = XDG.get_data_home().join("Steam").canonicalize() - && steam_root.is_dir() - { - Ok(steam_root) - } else if let Ok(steam_root) = XDG.get_data_home().join("steam").canonicalize() - && steam_root.is_dir() - { - Ok(steam_root) - } else { - bail!("Could not find canonical steam root!",) - } +pub fn get_all_potential_steam_main_dir_paths() -> Vec { + vec![ + get_home_dir().join(".steam/root"), + XDG.get_data_home().join("Steam"), + XDG.get_data_home().join("steam"), + ] +} + +pub fn chaperone_info_exists() -> bool { + get_all_potential_steam_main_dir_paths().iter().any(|p| { + p.canonicalize() + .is_ok_and(|p| p.join("config/chaperone_info.vrchap").is_file()) + }) } impl SteamLibraryFolder { pub fn get_folders() -> anyhow::Result> { - let libraryfolders_path = get_steam_main_dir_path()? - .join("steamapps/libraryfolders.vdf") - .canonicalize()?; - if !libraryfolders_path.is_file() { - bail!( - "Steam libraryfolders.vdf does not exist in its canonical location {}", - libraryfolders_path.to_string_lossy() - ); + if let Some(libraryfolders_path) = get_all_potential_steam_main_dir_paths() + .into_iter() + .filter_map(|p| p.join("steamapps/libraryfolders.vdf").canonicalize().ok()) + .find(|p| p.is_file()) + { + Self::get_folders_from_path(&libraryfolders_path) + } else { + bail!("Steam libraryfolders.vdf does not exist in any of its canonical locations"); } - Self::get_folders_from_path(&libraryfolders_path) } /// Do not use this: use get_folders() instead as it always uses Steam's diff --git a/src/util/steamvr_utils.rs b/src/util/steamvr_utils.rs deleted file mode 100644 index 67e3b43..0000000 --- a/src/util/steamvr_utils.rs +++ /dev/null @@ -1,5 +0,0 @@ -use crate::util::steam_library_folder::get_steam_main_dir_path; - -pub fn chaperone_info_exists() -> bool { - get_steam_main_dir_path().is_ok_and(|p| p.join("config/chaperone_info.vrchap").is_file()) -}