diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1f61b63..a1cba63 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: "debian:testing" +image: "debian:unstable" stages: - check diff --git a/README.md b/README.md index 68a2fa9..ae95ce9 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Also consider that due to the unstable nature of the app, it's possible to encou UI for building, configuring and running Monado, the open source OpenXR runtime. -Download the latest AppImage snapshot: [GitLab Pipelines](https://gitlab.com/gabmus/envision/-/pipelines) +Download the latest AppImage snapshot: [GitLab Pipelines](https://gitlab.com/gabmus/envision/-/pipelines?page=1&scope=all&ref=main) ## Running diff --git a/src/checkerr.rs b/src/checkerr.rs deleted file mode 100644 index 4f39bf7..0000000 --- a/src/checkerr.rs +++ /dev/null @@ -1,8 +0,0 @@ -#[macro_export] -macro_rules! checkerr { - ($ex:expr) => { - if $ex.is_err() { - return Err(()); - } - }; -} diff --git a/src/file_builders/active_runtime_json.rs b/src/file_builders/active_runtime_json.rs index 9076b0a..0f45807 100644 --- a/src/file_builders/active_runtime_json.rs +++ b/src/file_builders/active_runtime_json.rs @@ -1,5 +1,4 @@ use crate::{ - checkerr, file_utils::{copy_file, deserialize_file, get_writer, set_file_readonly}, paths::{get_backup_dir, get_xdg_config_dir, get_xdg_data_dir, SYSTEM_PREFIX}, profile::{Profile, XRServiceType}, @@ -97,9 +96,9 @@ fn build_steam_active_runtime() -> ActiveRuntime { } } -pub fn set_current_active_runtime_to_steam() -> Result<(), ()> { - checkerr!(set_file_readonly(&get_active_runtime_json_path(), false)); - checkerr!(dump_current_active_runtime(&build_steam_active_runtime())); +pub fn set_current_active_runtime_to_steam() -> anyhow::Result<()> { + set_file_readonly(&get_active_runtime_json_path(), false)?; + dump_current_active_runtime(&build_steam_active_runtime())?; Ok(()) } @@ -156,9 +155,9 @@ fn relativize_active_runtime_lib_path(ar: &ActiveRuntime, dest: &str) -> ActiveR res } -pub fn set_current_active_runtime_to_profile(profile: &Profile) -> Result<(), ()> { +pub fn set_current_active_runtime_to_profile(profile: &Profile) -> anyhow::Result<()> { let dest = get_active_runtime_json_path(); - checkerr!(set_file_readonly(&dest, false)); + set_file_readonly(&dest, false)?; backup_steam_active_runtime(); let pfx = profile.clone().prefix; let mut ar = build_profile_active_runtime(profile); @@ -166,8 +165,8 @@ pub fn set_current_active_runtime_to_profile(profile: &Profile) -> Result<(), () if pfx == SYSTEM_PREFIX { ar = relativize_active_runtime_lib_path(&ar, &dest); } - checkerr!(dump_current_active_runtime(&ar)); - checkerr!(set_file_readonly(&dest, true)); + dump_current_active_runtime(&ar)?; + set_file_readonly(&dest, true)?; Ok(()) } diff --git a/src/file_builders/openvrpaths_vrpath.rs b/src/file_builders/openvrpaths_vrpath.rs index 33da8c4..bc59a33 100644 --- a/src/file_builders/openvrpaths_vrpath.rs +++ b/src/file_builders/openvrpaths_vrpath.rs @@ -1,5 +1,4 @@ use crate::{ - checkerr, file_utils::{copy_file, deserialize_file, get_writer, set_file_readonly}, paths::{get_backup_dir, get_xdg_config_dir, get_xdg_data_dir}, profile::Profile, @@ -94,9 +93,9 @@ fn build_steam_openvrpaths() -> OpenVrPaths { } } -pub fn set_current_openvrpaths_to_steam() -> Result<(), ()> { - checkerr!(set_file_readonly(&get_openvrpaths_vrpath_path(), false)); - checkerr!(dump_current_openvrpaths(&build_steam_openvrpaths())); +pub fn set_current_openvrpaths_to_steam() -> anyhow::Result<()> { + set_file_readonly(&get_openvrpaths_vrpath_path(), false)?; + dump_current_openvrpaths(&build_steam_openvrpaths())?; Ok(()) } @@ -115,14 +114,12 @@ pub fn build_profile_openvrpaths(profile: &Profile) -> OpenVrPaths { } } -pub fn set_current_openvrpaths_to_profile(profile: &Profile) -> Result<(), ()> { +pub fn set_current_openvrpaths_to_profile(profile: &Profile) -> anyhow::Result<()> { let dest = get_openvrpaths_vrpath_path(); - checkerr!(set_file_readonly(&dest, false)); + set_file_readonly(&dest, false)?; backup_steam_openvrpaths(); - checkerr!(dump_current_openvrpaths(&build_profile_openvrpaths( - profile - ))); - checkerr!(set_file_readonly(&dest, true)); + dump_current_openvrpaths(&build_profile_openvrpaths(profile))?; + set_file_readonly(&dest, true)?; Ok(()) } diff --git a/src/linux_distro.rs b/src/linux_distro.rs index d866289..b112cc1 100644 --- a/src/linux_distro.rs +++ b/src/linux_distro.rs @@ -9,39 +9,56 @@ pub enum LinuxDistro { Debian, Fedora, Gentoo, + // TODO: add Nix, + Suse, } -pub fn get_distro() -> Option { - if let Some(mut reader) = get_reader("/etc/issue") { - let mut buf = String::default(); - if reader.read_to_string(&mut buf).is_ok() { - buf = buf.trim().to_lowercase(); - if buf.contains("arch linux") - || buf.contains("manjaro") - || buf.contains("steamos") - || buf.contains("steam os") - { - return Some(LinuxDistro::Arch); - } - if buf.contains("debian") - || buf.contains("ubuntu") - || buf.contains("mint") - || buf.contains("elementary") - || buf.contains("pop") - { - return Some(LinuxDistro::Debian); - } - if buf.contains("fedora") || buf.contains("nobara") { - return Some(LinuxDistro::Fedora); - } - if buf.contains("gentoo") { - return Some(LinuxDistro::Gentoo); - } - if buf.contains("alpine") || buf.contains("postmarket") { - return Some(LinuxDistro::Alpine); +impl LinuxDistro { + pub fn get() -> Option { + if let Some(mut reader) = get_reader("/etc/issue") { + let mut buf = String::default(); + if reader.read_to_string(&mut buf).is_ok() { + buf = buf.trim().to_lowercase(); + if buf.contains("arch linux") + || buf.contains("manjaro") + || buf.contains("steamos") + || buf.contains("steam os") + { + return Some(Self::Arch); + } + if buf.contains("debian") + || buf.contains("ubuntu") + || buf.contains("mint") + || buf.contains("elementary") + || buf.contains("pop") + { + return Some(Self::Debian); + } + if buf.contains("fedora") || buf.contains("nobara") { + return Some(Self::Fedora); + } + if buf.contains("gentoo") { + return Some(Self::Gentoo); + } + if buf.contains("alpine") || buf.contains("postmarket") { + return Some(Self::Alpine); + } + + // TODO: detect suse, sles, rhel, nix } } + + None } - None + pub fn install_command(&self, packages: &[String]) -> String { + match self { + Self::Arch => format!("sudo pacman -Syu {}", packages.join(" ")), + Self::Alpine => format!("sudo apk add {}", packages.join(" ")), + Self::Debian => format!("sudo apt install {}", packages.join(" ")), + Self::Fedora => format!("sudo dnf install {}", packages.join(" ")), + Self::Gentoo => format!("sudo emerge {}", packages.join(" ")), + Self::Suse => format!("sudo zypper install {}", packages.join(" ")), + } + } } diff --git a/src/main.rs b/src/main.rs index 43d775e..7e03d81 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,6 @@ use ui::app::{App, AppInit}; pub mod adb; pub mod build_tools; pub mod builders; -pub mod checkerr; pub mod cmd_runner; pub mod config; pub mod constants; @@ -49,7 +48,7 @@ fn restore_steam_xr_files() { if !file_builders::active_runtime_json::is_steam(&ar) { match set_current_active_runtime_to_steam() { Ok(_) => {} - Err(_) => println!("Warning: failed to restore active runtime to steam!"), + Err(e) => eprintln!("Warning: failed to restore active runtime to steam: {e}"), }; } } @@ -57,7 +56,7 @@ fn restore_steam_xr_files() { if !file_builders::openvrpaths_vrpath::is_steam(&ovrp) { match set_current_openvrpaths_to_steam() { Ok(_) => {} - Err(_) => println!("Warning: failed to restore openvrpaths to steam!"), + Err(e) => eprintln!("Warning: failed to restore openvrpaths to steam: {e}"), }; } } diff --git a/src/ui/alert.rs b/src/ui/alert.rs index d160997..c81b711 100644 --- a/src/ui/alert.rs +++ b/src/ui/alert.rs @@ -1,7 +1,10 @@ -use gtk::traits::{GtkApplicationExt, GtkWindowExt}; +use gtk::{ + prelude::IsA, + traits::{GtkApplicationExt, GtkWindowExt}, +}; use relm4::{adw::traits::MessageDialogExt, prelude::*}; -pub fn alert(title: &str, msg: Option<&str>, parent: Option<>k::Window>) { +fn alert_base(title: &str, msg: Option<&str>, parent: Option<>k::Window>) -> adw::MessageDialog { let d = adw::MessageDialog::builder() .modal(true) .heading(title) @@ -15,5 +18,23 @@ pub fn alert(title: &str, msg: Option<&str>, parent: Option<>k::Window>) { d.set_transient_for(gtk::Application::default().active_window().as_ref()); } d.add_response("ok", "_Ok"); + d +} + +pub fn alert(title: &str, msg: Option<&str>, parent: Option<>k::Window>) { + let d = alert_base(title, msg, parent); + d.present(); +} + +pub fn alert_w_widget( + title: &str, + msg: Option<&str>, + widget: Option<>k::Widget>, + parent: Option<>k::Window>, +) { + let d = alert_base(title, msg, parent); + if let Some(w) = widget { + d.set_extra_child(Some(w)); + } d.present(); } diff --git a/src/ui/app.rs b/src/ui/app.rs index f77967a..ff06ee8 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -1,5 +1,5 @@ use super::about_dialog::AboutDialog; -use super::alert::alert; +use super::alert::{alert, alert_w_widget}; use super::build_window::{BuildStatus, BuildWindow}; use super::debug_view::{DebugView, DebugViewMsg}; use super::fbt_config_editor::{FbtConfigEditor, FbtConfigEditorInit, FbtConfigEditorMsg}; @@ -8,7 +8,7 @@ use super::job_worker::job::WorkerJob; use super::job_worker::JobWorker; use super::libsurvive_setup_window::LibsurviveSetupWindow; use super::main_view::MainViewMsg; -use super::util::open_with_default_handler; +use super::util::{copy_text, open_with_default_handler}; use super::wivrn_conf_editor::{WivrnConfEditor, WivrnConfEditorInit, WivrnConfEditorMsg}; use crate::builders::build_basalt::get_build_basalt_jobs; use crate::builders::build_libsurvive::get_build_libsurvive_jobs; @@ -34,7 +34,7 @@ use crate::file_builders::openvrpaths_vrpath::{ set_current_openvrpaths_to_profile, set_current_openvrpaths_to_steam, }; use crate::file_utils::setcap_cap_sys_nice_eip; -use crate::linux_distro::get_distro; +use crate::linux_distro::LinuxDistro; use crate::log_parser::MonadoLog; use crate::paths::{get_data_dir, get_ipc_file_path}; use crate::profile::{Profile, XRServiceType}; @@ -162,18 +162,22 @@ impl App { pub fn start_xrservice(&mut self, sender: ComponentSender, debug: bool) { let prof = self.get_selected_profile(); - if set_current_active_runtime_to_profile(&prof).is_err() { + if let Err(e) = set_current_active_runtime_to_profile(&prof) { alert( "Failed to start XR Service", - Some("Error setting current active runtime to profile"), + Some(&format!( + "Error setting current active runtime to profile: {e}" + )), Some(&self.app_win.clone().upcast::()), ); return; } - if set_current_openvrpaths_to_profile(&prof).is_err() { + if let Err(e) = set_current_openvrpaths_to_profile(&prof) { alert( "Failed to start XR Service", - Some("Error setting current openvrpaths file to profile"), + Some(&format!( + "Error setting current openvrpaths file to profile: {e}" + )), Some(&self.app_win.clone().upcast::()), ); return; @@ -218,17 +222,17 @@ impl App { } pub fn restore_openxr_openvr_files(&self) { - if set_current_active_runtime_to_steam().is_err() { + if let Err(e) = set_current_active_runtime_to_steam() { alert( "Could not restore Steam active runtime", - None, + Some(&format!("{e}")), Some(&self.app_win.clone().upcast::()), ); } - if set_current_openvrpaths_to_steam().is_err() { + if let Err(e) = set_current_openvrpaths_to_steam() { alert( "Could not restore Steam openvrpaths", - None, + Some(&format!("{e}")), Some(&self.app_win.clone().upcast::()), ); }; @@ -466,26 +470,101 @@ impl SimpleComponent for App { if !missing_deps.is_empty() { missing_deps.sort_unstable(); missing_deps.dedup(); // dedup only works if sorted, hence the above - let distro = get_distro(); - alert( - "Missing dependencies:", - Some( + let distro = LinuxDistro::get(); + let (missing_package_list, install_missing_widget): ( + String, + Option, + ) = if let Some(d) = distro { + ( missing_deps .iter() .map(|dep| { - if let Some(d) = distro { - return dep - .packages - .get(&d) - .unwrap_or_else(|| &dep.name) - .clone(); - } - dep.name.clone() + dep.packages.get(&d).unwrap_or_else(|| &dep.name).clone() }) .collect::>() - .join(", ") - .as_str(), - ), + .join(", "), + { + let packages = missing_deps + .iter() + .filter_map(|dep| { + dep.packages.get(&d).and_then(|s| Some(s.clone())) + }) + .collect::>(); + if packages.is_empty() { + None + } else { + let cmd = d.install_command(&packages); + { + let container = gtk::Box::builder() + .orientation(gtk::Orientation::Horizontal) + .spacing(6) + .build(); + let btn = gtk::Button::builder() + .css_classes(["flat", "circular"]) + .tooltip_text("Copy") + .icon_name("edit-copy-symbolic") + .vexpand(false) + .hexpand(false) + .valign(gtk::Align::Center) + .halign(gtk::Align::Center) + .build(); + btn.connect_clicked( + clone!(@strong cmd => move |_| copy_text(&cmd)), + ); + container.append( + >k::ScrolledWindow::builder() + .vscrollbar_policy(gtk::PolicyType::Never) + .hscrollbar_policy(gtk::PolicyType::Automatic) + .css_classes(["card"]) + .overflow(gtk::Overflow::Hidden) + .child( + >k::TextView::builder() + .hexpand(true) + .vexpand(false) + .monospace(true) + .editable(false) + .left_margin(6) + .right_margin(6) + .top_margin(6) + .bottom_margin(18) + .buffer( + >k::TextBuffer::builder() + .text(&cmd) + .enable_undo(false) + .build(), + ) + .build(), + ) + .build(), + ); + container.append(&btn); + Some(container.upcast()) + } + } + }, + ) + } else { + ( + missing_deps + .iter() + .map(|dep| dep.name.clone()) + .collect::>() + .join(", "), + None, + ) + }; + alert_w_widget( + "Missing dependencies:", + Some(&format!( + "{}{}", + missing_package_list, + if install_missing_widget.is_some() { + "\n\nYou can install them with the following command:" + } else { + "" + } + )), + install_missing_widget.as_ref(), Some(&self.app_win.clone().upcast::()), ); return;