new file: Makefile

new file:   TODO.md
	modified:   src/config.rs
	modified:   src/detect.rs
	modified:   src/diagnose.rs
	new file:   src/gui.rs
	modified:   src/main.rs
	modified:   src/service.rs
	modified:   src/setup.rs
	modified:   src/tray.rs
	new file:   src/util.rs
	new file:   umutray.desktop
This commit is contained in:
funman300
2026-04-17 23:12:47 -07:00
parent 4c918e673b
commit f2f584febf
12 changed files with 1471 additions and 113 deletions
+42 -46
View File
@@ -4,38 +4,30 @@ use std::os::unix::fs::MetadataExt;
use std::path::Path;
use std::process::{Command, Stdio};
struct Check {
label: String,
pass: bool,
detail: String,
#[derive(Debug, Clone)]
pub struct CheckResult {
pub label: String,
pub pass: bool,
pub detail: String,
}
impl Check {
impl CheckResult {
fn pass(label: impl Into<String>, detail: impl Into<String>) -> Self {
Self {
label: label.into(),
pass: true,
detail: detail.into(),
}
Self { label: label.into(), pass: true, detail: detail.into() }
}
fn fail(label: impl Into<String>, detail: impl Into<String>) -> Self {
Self {
label: label.into(),
pass: false,
detail: detail.into(),
}
Self { label: label.into(), pass: false, detail: detail.into() }
}
}
pub fn run(config: &Config, name: Option<&str>) -> Result<()> {
let mut checks: Vec<Check> = vec![
pub fn run_checks(config: &Config, name: Option<&str>) -> Result<Vec<CheckResult>> {
let mut checks = vec![
global_umu_check(),
global_vulkan_check(),
global_display_check(),
compat_dir_check(config),
wineserver_check(config),
];
let launchers: Vec<&Launcher> = if let Some(n) = name {
let l = config
.find(n)
@@ -47,7 +39,11 @@ pub fn run(config: &Config, name: Option<&str>) -> Result<()> {
for l in launchers {
checks.extend(launcher_checks(l));
}
Ok(checks)
}
pub fn run(config: &Config, name: Option<&str>) -> Result<()> {
let checks = run_checks(config, name)?;
let mut issues = 0u32;
println!();
for c in &checks {
@@ -71,14 +67,14 @@ pub fn run(config: &Config, name: Option<&str>) -> Result<()> {
Ok(())
}
fn global_umu_check() -> Check {
fn global_umu_check() -> CheckResult {
match which("umu-run") {
Some(p) => Check::pass("umu-run", format!("found at {p}")),
None => Check::fail("umu-run", "not found — install umu-launcher"),
Some(p) => CheckResult::pass("umu-run", format!("found at {p}")),
None => CheckResult::fail("umu-run", "not found — install umu-launcher"),
}
}
fn global_vulkan_check() -> Check {
fn global_vulkan_check() -> CheckResult {
let ok = Command::new("vulkaninfo")
.arg("--summary")
.stdout(Stdio::null())
@@ -87,33 +83,33 @@ fn global_vulkan_check() -> Check {
.map(|s| s.success())
.unwrap_or(false);
if ok {
Check::pass("vulkan", "vulkaninfo OK")
CheckResult::pass("vulkan", "vulkaninfo OK")
} else {
Check::fail(
CheckResult::fail(
"vulkan",
"vulkaninfo failed — check GPU drivers / vulkan-tools",
)
}
}
fn global_display_check() -> Check {
fn global_display_check() -> CheckResult {
let display = std::env::var("DISPLAY").ok();
let wayland = std::env::var("WAYLAND_DISPLAY").ok();
match (display, wayland) {
(Some(d), Some(_)) => Check::pass("display", format!("XWayland (DISPLAY={d})")),
(Some(d), None) => Check::pass("display", format!("X11 (DISPLAY={d})")),
(None, Some(_)) => Check::fail(
(Some(d), Some(_)) => CheckResult::pass("display", format!("XWayland (DISPLAY={d})")),
(Some(d), None) => CheckResult::pass("display", format!("X11 (DISPLAY={d})")),
(None, Some(_)) => CheckResult::fail(
"display",
"Wayland session but DISPLAY unset; XWayland needed",
),
(None, None) => Check::fail("display", "neither DISPLAY nor WAYLAND_DISPLAY set"),
(None, None) => CheckResult::fail("display", "neither DISPLAY nor WAYLAND_DISPLAY set"),
}
}
fn compat_dir_check(config: &Config) -> Check {
fn compat_dir_check(config: &Config) -> CheckResult {
let n = count_ge_proton(&config.proton_compat_dir);
if config.proton_version == "GE-Proton" {
Check::pass(
CheckResult::pass(
"proton",
format!(
"tracking latest; {n} version(s) in {}",
@@ -123,9 +119,9 @@ fn compat_dir_check(config: &Config) -> Check {
} else {
let path = config.proton_compat_dir.join(&config.proton_version);
if path.exists() {
Check::pass("proton", format!("{} installed", config.proton_version))
CheckResult::pass("proton", format!("{} installed", config.proton_version))
} else {
Check::fail(
CheckResult::fail(
"proton",
format!(
"{} missing — run: umutray update-proton --version={}",
@@ -136,19 +132,19 @@ fn compat_dir_check(config: &Config) -> Check {
}
}
fn wineserver_check(config: &Config) -> Check {
fn wineserver_check(config: &Config) -> CheckResult {
let count = wineserver_count();
if count == 0 {
return Check::pass("wine procs", "no wineserver running");
return CheckResult::pass("wine procs", "no wineserver running");
}
let any_running = config.launchers.iter().any(crate::launcher::is_running);
if any_running {
Check::pass(
CheckResult::pass(
"wine procs",
format!("{count} wineserver process(es); launcher active"),
)
} else {
Check::fail(
CheckResult::fail(
"wine procs",
format!("{count} stale wineserver process(es) — try: umutray kill"),
)
@@ -165,12 +161,12 @@ fn wineserver_count() -> usize {
.unwrap_or(0)
}
fn launcher_checks(l: &Launcher) -> Vec<Check> {
fn launcher_checks(l: &Launcher) -> Vec<CheckResult> {
let mut out = Vec::new();
let tag = format!("[{}]", l.name);
if !l.prefix_dir.exists() {
out.push(Check::fail(
out.push(CheckResult::fail(
format!("{tag} prefix"),
format!(
"{} missing — run: umutray setup {}",
@@ -180,34 +176,34 @@ fn launcher_checks(l: &Launcher) -> Vec<Check> {
));
return out;
}
out.push(Check::pass(
out.push(CheckResult::pass(
format!("{tag} prefix"),
l.prefix_dir.display().to_string(),
));
let exe = l.full_exe_path();
if exe.exists() {
out.push(Check::pass(format!("{tag} exe"), "installed"));
out.push(CheckResult::pass(format!("{tag} exe"), "installed"));
} else {
out.push(Check::fail(
out.push(CheckResult::fail(
format!("{tag} exe"),
format!("missing — run: umutray setup {}", l.name),
));
}
if is_owned_by_current_user(&l.prefix_dir) {
out.push(Check::pass(format!("{tag} owner"), "owned by current user"));
out.push(CheckResult::pass(format!("{tag} owner"), "owned by current user"));
} else {
out.push(Check::fail(
out.push(CheckResult::fail(
format!("{tag} owner"),
"not owned by current user",
));
}
if crate::launcher::is_running(l) {
out.push(Check::pass(format!("{tag} process"), "currently running"));
out.push(CheckResult::pass(format!("{tag} process"), "currently running"));
} else {
out.push(Check::pass(format!("{tag} process"), "not running"));
out.push(CheckResult::pass(format!("{tag} process"), "not running"));
}
out