diff --git a/Cargo.lock b/Cargo.lock index 2c358b4..a18c8ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,6 +32,15 @@ version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading 0.8.5", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -271,6 +280,7 @@ name = "envision" version = "0.1.0" dependencies = [ "anyhow", + "ash", "gettext-rs", "git2", "gtk4", diff --git a/Cargo.toml b/Cargo.toml index b3ba8aa..5f63955 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,3 +29,4 @@ xdg = "2.5.2" openxr = { git = "https://github.com/galister/openxrs", rev = "af4a55d", features = [ "linked", ] } +ash = "0.38.0" diff --git a/meson.build b/meson.build index 7da240b..f1edb57 100644 --- a/meson.build +++ b/meson.build @@ -13,7 +13,7 @@ base_id = 'org.gabmus.envision' pretty_name = 'Envision' upstream_repo = 'https://gitlab.com/gabmus/envision' author = 'The Envision Team' -description = 'GUI for Monado' # temporary +description = 'Orchestrator for the free XR stack' # temporary dependency('glib-2.0', version: '>= 2.66') dependency('gio-2.0', version: '>= 2.66') @@ -55,8 +55,10 @@ endif meson.add_dist_script( 'build-aux/dist-vendor.sh', - meson.project_build_root() / 'meson-dist' / meson.project_name() + '-' + version, - meson.project_source_root() + meson.project_build_root() / 'meson-dist' / meson.project_name() + + '-' + + version, + meson.project_source_root(), ) global_conf = configuration_data() @@ -71,6 +73,11 @@ global_conf.set('AUTHOR', author) global_conf.set('PRETTY_NAME', pretty_name) global_conf.set('CMD_NAME', meson.project_name()) global_conf.set('REPO_URL', upstream_repo) +global_conf.set('ISSUES_URL', upstream_repo + '/-/issues') +global_conf.set( + 'BUILD_DATETIME', + run_command('date', '-Iseconds', '--utc', check: true).stdout().strip(), +) subdir('data') subdir('po') diff --git a/src/constants.rs.in b/src/constants.rs.in index a988e34..0dee19e 100644 --- a/src/constants.rs.in +++ b/src/constants.rs.in @@ -9,11 +9,13 @@ pub const PKG_DATA_DIR: &str = "@PKGDATADIR@"; pub const RESOURCES: &str = concat!("@PKGDATADIR@", "/resources.gresource"); pub const CMD_NAME: &str = "@CMD_NAME@"; pub const VERSION: &str = "@VERSION@"; -pub const REPO_URL: &str = "@UPSTREAM_REPO_URL@"; +pub const REPO_URL: &str = "@REPO_URL@"; +pub const ISSUES_URL: &str = "@ISSUES_URL@"; pub const SINGLE_DEVELOPER: &str = "@AUTHOR@"; pub const GETTEXT_PACKAGE: &str = "@GETTEXT_PACKAGE@"; pub const LOCALE_DIR: &str = "@LOCALEDIR@"; pub const BUILD_PROFILE: &str = "@PROFILE@"; +pub const BUILD_DATETIME: &str = "@BUILD_DATETIME@"; pub fn get_developers() -> Vec { vec!["Gabriele Musco ".to_string()] diff --git a/src/linux_distro.rs b/src/linux_distro.rs index 1df1035..2f0079d 100644 --- a/src/linux_distro.rs +++ b/src/linux_distro.rs @@ -1,5 +1,6 @@ use crate::file_utils::get_reader; use std::{ + fmt::Display, io::{BufRead, Read}, path::Path, }; @@ -15,6 +16,19 @@ pub enum LinuxDistro { Suse, } +impl Display for LinuxDistro { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Self::Alpine => "Alpine", + Self::Arch => "Arch", + Self::Debian => "Debian", + Self::Fedora => "Fedora", + Self::Gentoo => "Gentoo", + Self::Suse => "Suse", + }) + } +} + impl LinuxDistro { pub fn get() -> Option { Self::get_from_etc_os_release().or_else(Self::get_from_etc_issue) @@ -24,12 +38,40 @@ impl LinuxDistro { Self::get_from_etc_os_release_file(Path::new("/etc/os-release")) } + // does it make sense to be here? + pub fn get_specific_distro() -> Option { + let mut name: Option = None; + if let Some(mut reader) = get_reader(Path::new("/etc/os-release")) { + let mut buf = String::new(); + loop { + match reader.read_line(&mut buf) { + Ok(0) | Err(_) => break, + Ok(_) if buf.starts_with("PRETTY_NAME=\"") => { + return buf + .split('=') + .last() + .map(|b| b.trim().trim_matches('"').trim().to_string()); + } + Ok(_) if buf.starts_with("NAME=\"") => { + name = buf + .split('=') + .last() + .map(|b| b.trim().trim_matches('"').trim().to_string()); + } + _ => {} + }; + buf.clear(); + } + } + name + } + fn get_from_etc_os_release_file(fp: &Path) -> Option { if let Some(mut reader) = get_reader(fp) { let mut buf = String::new(); loop { match reader.read_line(&mut buf) { - Ok(0) => break, + Ok(0) | Err(_) => break, Ok(_) => { if buf.starts_with("NAME=\"") || buf.starts_with("ID=\"") @@ -39,7 +81,7 @@ impl LinuxDistro { .split('=') .last() .unwrap_or_default() - .to_string() + .trim() .trim_matches('"') .to_lowercase(); let res = Self::name_matcher(&name); @@ -48,8 +90,8 @@ impl LinuxDistro { } } } - Err(_) => break, } + buf.clear(); } } None diff --git a/src/main.rs b/src/main.rs index 9c488dd..558afa7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -44,6 +44,7 @@ pub mod steam_linux_runtime_injector; pub mod steamvr_utils; pub mod termcolor; pub mod ui; +pub mod vulkaninfo; pub mod xdg; pub mod xr_devices; diff --git a/src/ui/about_dialog.rs b/src/ui/about_dialog.rs index faf2917..d8efc40 100644 --- a/src/ui/about_dialog.rs +++ b/src/ui/about_dialog.rs @@ -1,5 +1,14 @@ -use crate::constants::{get_developers, APP_ID, APP_NAME, REPO_URL, SINGLE_DEVELOPER, VERSION}; +use crate::{ + constants::{ + get_developers, APP_ID, APP_NAME, BUILD_DATETIME, ISSUES_URL, REPO_URL, SINGLE_DEVELOPER, + VERSION, + }, + linux_distro::LinuxDistro, + vulkaninfo::gpu_names, + xdg::XDG, +}; use relm4::prelude::*; +use std::env; pub fn create_about_dialog() -> adw::AboutDialog { adw::AboutDialog::builder() @@ -8,7 +17,55 @@ pub fn create_about_dialog() -> adw::AboutDialog { .license_type(gtk::License::Agpl30) .version(VERSION) .website(REPO_URL) + .issue_url(ISSUES_URL) .developer_name(SINGLE_DEVELOPER) .developers(get_developers()) .build() } + +pub fn populate_debug_info(dialog: &adw::AboutDialog) { + if dialog.debug_info().len() > 0 { + return; + } + let distro_family = LinuxDistro::get(); + let distro = LinuxDistro::get_specific_distro(); + dialog.set_debug_info( + &[ + format!("Version: {VERSION}"), + format!("Build time: {BUILD_DATETIME}"), + format!( + "Operating system: {d} ({f})", + d = distro.unwrap_or("unknown".into()), + f = distro_family + .map(|f| f.to_string()) + .unwrap_or("unknown".into()) + ), + format!( + "Session type: {}", + env::var("XDG_SESSION_TYPE").unwrap_or("unknown".into()) + ), + format!( + "Desktop: {}", + env::var("XDG_CURRENT_DESKTOP").unwrap_or("unknown".into()) + ), + format!( + "GPUs: {}", + unsafe { gpu_names() } + .ok() + .map(|names| names.join(", ")) + .unwrap_or("unknown".into()) + ), + format!( + "Steam found: {}", + if XDG.get_data_home().join("Steam").is_dir() + || XDG.get_data_home().join("steam").is_dir() + { + "yes" + } else { + "no" + } + ), + ] + .join("\n"), + ); +} diff --git a/src/ui/app.rs b/src/ui/app.rs index 1f963fd..43897fe 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -41,7 +41,7 @@ use crate::stateless_action; use crate::steam_linux_runtime_injector::{ restore_runtime_entrypoint, set_runtime_entrypoint_launch_opts_from_profile, }; -use crate::ui::about_dialog::create_about_dialog; +use crate::ui::about_dialog::{create_about_dialog, populate_debug_info}; use crate::ui::build_window::{BuildWindowInit, BuildWindowMsg, BuildWindowOutMsg}; use crate::ui::debug_view::{DebugViewInit, DebugViewOutMsg}; use crate::ui::libsurvive_setup_window::LibsurviveSetupMsg; @@ -942,6 +942,7 @@ impl SimpleComponent for App { #[strong(rename_to = app_win)] model.app_win, move |_| { + populate_debug_info(&about_dialog); about_dialog.present(Some(&app_win)); } ) diff --git a/src/vulkaninfo.rs b/src/vulkaninfo.rs new file mode 100644 index 0000000..15b820a --- /dev/null +++ b/src/vulkaninfo.rs @@ -0,0 +1,29 @@ +use ash::{ + vk::{ApplicationInfo, InstanceCreateInfo}, + Entry, +}; + +/// # Safety +/// +/// Dlopens the vulkan library, so this is inherently unsafe. Should be fine in +/// most circumstances +pub unsafe fn gpu_names() -> anyhow::Result> { + let entry = Entry::load()?; + let instance = entry.create_instance( + &InstanceCreateInfo::default().application_info(&ApplicationInfo::default()), + None, + )?; + let names = instance + .enumerate_physical_devices()? + .into_iter() + .filter_map(|d| { + instance + .get_physical_device_properties(d) + .device_name_as_c_str() + .ok() + .map(|cs| cs.to_string_lossy().to_string()) + }) + .collect(); + instance.destroy_instance(None); + Ok(names) +}