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/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..ba011ad 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}; @@ -466,26 +466,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;