feat(diagnostics): add OS / distro / install method / deps section

New v2_get_system_info command and matching UI section in the
System Diagnostics panel. What it reports:

- os (linux/macos/windows)
- arch (x86_64, aarch64)
- kernel_version from /proc/sys/kernel/osrelease
- Linux distro: id, version_id, pretty_name from /etc/os-release
  (falls back to /run/host/os-release so it works inside Flatpak)
- install_method: flatpak / snap / appimage / dev / native, detected
  via FLATPAK_ID + /.flatpak-info, SNAP, APPIMAGE env vars, and
  debug_assertions as the last fallback
- flatpak_runtime + version: parsed from /.flatpak-info
- loaded library versions (from /proc/self/maps, symlink-resolved):
  webkit2gtk-4.1, gtk-3 (or 4), glibc, alsa, pipewire, pulseaudio

All info is derived from local files/env; no sensitive data, no IDs,
no hashes. Included in the clipboard export.

cc
This commit is contained in:
vicrodh committed 2026-04-16 11:14:26 -06:00
1 parent 85057589e6
commit d5e3fff45c
8 files changed
+248 -2

No files matched your search

+163
View File
@@ -166,3 +166,166 @@ pub fn v2_get_runtime_diagnostics(
app_version: env!("CARGO_PKG_VERSION").to_string(),
})
}
// ==================== System Info ====================
#[derive(Debug, Clone, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SystemInfo {
pub os: String,
pub arch: String,
pub kernel_version: Option<String>,
pub distro_id: Option<String>,
pub distro_version_id: Option<String>,
pub distro_pretty_name: Option<String>,
pub install_method: String,
pub flatpak_runtime: Option<String>,
pub flatpak_runtime_version: Option<String>,
pub webkit2gtk_version: Option<String>,
pub gtk_version: Option<String>,
pub glibc_version: Option<String>,
pub alsa_version: Option<String>,
pub pipewire_version: Option<String>,
pub pulseaudio_version: Option<String>,
}
/// Parse /etc/os-release (or the Flatpak host equivalent) into key/value pairs.
fn read_os_release() -> std::collections::HashMap<String, String> {
let mut map = std::collections::HashMap::new();
// Try Flatpak-exposed host file first, then the normal path.
let candidates = ["/run/host/os-release", "/etc/os-release"];
for path in candidates {
if let Ok(text) = std::fs::read_to_string(path) {
for line in text.lines() {
if let Some(idx) = line.find('=') {
let key = line[..idx].trim().to_string();
let mut value = line[idx + 1..].trim().to_string();
if value.starts_with('"') && value.ends_with('"') && value.len() >= 2 {
value = value[1..value.len() - 1].to_string();
}
map.insert(key, value);
}
}
if !map.is_empty() {
return map;
}
}
}
map
}
fn detect_kernel_version() -> Option<String> {
std::fs::read_to_string("/proc/sys/kernel/osrelease")
.ok()
.map(|s| s.trim().to_string())
}
fn detect_install_method() -> (String, Option<String>, Option<String>) {
// (method, flatpak_runtime, flatpak_runtime_version)
if std::env::var("FLATPAK_ID").is_ok() || std::path::Path::new("/.flatpak-info").exists() {
let mut runtime = None;
let mut runtime_version = None;
if let Ok(text) = std::fs::read_to_string("/.flatpak-info") {
for line in text.lines() {
if let Some(value) = line.strip_prefix("runtime=") {
let v = value.trim();
if let Some(slash) = v.rfind('/') {
runtime = Some(v[..slash].to_string());
runtime_version = Some(v[slash + 1..].to_string());
} else {
runtime = Some(v.to_string());
}
}
}
}
return ("flatpak".to_string(), runtime, runtime_version);
}
if std::env::var("SNAP").is_ok() {
return ("snap".to_string(), None, None);
}
if std::env::var("APPIMAGE").is_ok() {
return ("appimage".to_string(), None, None);
}
if cfg!(debug_assertions) {
return ("dev".to_string(), None, None);
}
("native".to_string(), None, None)
}
/// Extract the best-available version string from the filename of a shared
/// library loaded by the current process. Looks for patterns like
/// `libfoo.so.0.15.7` → `0.15.7`, or `libfoo.so.2` → `2`.
/// Returns `None` if the library isn't mapped.
fn detect_loaded_lib_version(lib_name_stem: &str) -> Option<String> {
let maps = std::fs::read_to_string("/proc/self/maps").ok()?;
let mut best: Option<String> = None;
for line in maps.lines() {
// Last column is the path (may contain spaces, very rare).
let path = line.splitn(6, ' ').nth(5).unwrap_or("").trim();
if path.is_empty() {
continue;
}
let filename = std::path::Path::new(path)
.file_name()
.and_then(|s| s.to_str())
.unwrap_or("");
if !filename.starts_with(lib_name_stem) {
continue;
}
// filename example: "libwebkit2gtk-4.1.so.0.15.7"
// Strip the "lib_name_stem.so" prefix and leading dot.
let tail = match filename.split_once(".so") {
Some((_, rest)) => rest.trim_start_matches('.'),
None => continue,
};
if tail.is_empty() {
continue;
}
// Resolve symlink target if possible — often the real file carries
// a fuller version than the SONAME alias.
if let Ok(real) = std::fs::canonicalize(path) {
if let Some(real_name) = real.file_name().and_then(|s| s.to_str()) {
if let Some((_, rest)) = real_name.split_once(".so") {
let real_tail = rest.trim_start_matches('.');
if !real_tail.is_empty() {
best = Some(real_tail.to_string());
continue;
}
}
}
}
best.get_or_insert_with(|| tail.to_string());
}
best
}
#[tauri::command]
pub fn v2_get_system_info() -> Result<SystemInfo, RuntimeError> {
let os = std::env::consts::OS.to_string();
let arch = std::env::consts::ARCH.to_string();
let (install_method, flatpak_runtime, flatpak_runtime_version) = detect_install_method();
let osr = read_os_release();
Ok(SystemInfo {
os,
arch,
kernel_version: detect_kernel_version(),
distro_id: osr.get("ID").cloned(),
distro_version_id: osr.get("VERSION_ID").cloned(),
distro_pretty_name: osr
.get("PRETTY_NAME")
.cloned()
.or_else(|| osr.get("NAME").cloned()),
install_method,
flatpak_runtime,
flatpak_runtime_version,
// Runtime shared-library versions, parsed from /proc/self/maps.
webkit2gtk_version: detect_loaded_lib_version("libwebkit2gtk-4.1"),
gtk_version: detect_loaded_lib_version("libgtk-3")
.or_else(|| detect_loaded_lib_version("libgtk-4")),
glibc_version: detect_loaded_lib_version("libc"),
alsa_version: detect_loaded_lib_version("libasound"),
pipewire_version: detect_loaded_lib_version("libpipewire-0.3"),
pulseaudio_version: detect_loaded_lib_version("libpulse"),
})
}
+1
View File
@@ -1361,6 +1361,7 @@ pub fn run() {
commands_v2::v2_set_visualizer_enabled,
commands_v2::v2_get_developer_settings,
commands_v2::v2_get_runtime_diagnostics,
commands_v2::v2_get_system_info,
commands_v2::v2_set_developer_force_dmabuf,
commands_v2::v2_get_graphics_settings,
commands_v2::v2_get_graphics_startup_status,