feat(gui): auto-detect games in Wine prefix and browse for exe
- detect::scan_games_in_prefix() walks drive_c/Program Files and
Program Files (x86) up to depth 4, skipping Windows system dirs,
the launcher's own exe, and already-configured games
- Empty game list now shows "No games added yet." with two actions:
· Scan for games — async scan of the Wine prefix, results appear
as a clickable list with + buttons to add each found exe
· Browse exe… — native file picker (zenity/kdialog) opening in
drive_c/, computes the relative path automatically
- Add-game form gains a Browse… button to pick exe from the prefix
- util::pick_file() added alongside pick_folder()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+95
-2
@@ -1,7 +1,7 @@
|
||||
use crate::config::Config;
|
||||
use crate::config::{Config, Launcher};
|
||||
use anyhow::Result;
|
||||
use owo_colors::OwoColorize;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -36,6 +36,99 @@ pub fn scan_for_gui(config: &Config) -> Vec<DetectHit> {
|
||||
hits
|
||||
}
|
||||
|
||||
/// Directories inside drive_c that contain Windows system files, not games.
|
||||
const SYSTEM_DIRS: &[&str] = &[
|
||||
"windows",
|
||||
"users",
|
||||
"programdata",
|
||||
"internet explorer",
|
||||
"windows media player",
|
||||
"windowspowershell",
|
||||
"microsoft.net",
|
||||
"common files",
|
||||
"microsoft",
|
||||
"windows nt",
|
||||
"windowsapps",
|
||||
];
|
||||
|
||||
/// Scan a launcher's Wine prefix for installed game executables.
|
||||
/// Returns (display_name, path_relative_to_drive_c) pairs, sorted alphabetically,
|
||||
/// excluding the launcher's own exe and any already-configured games.
|
||||
pub fn scan_games_in_prefix(launcher: &Launcher) -> Vec<(String, String)> {
|
||||
let drive_c = launcher.prefix_dir.join("drive_c");
|
||||
if !drive_c.exists() {
|
||||
return vec![];
|
||||
}
|
||||
let search_dirs = [
|
||||
drive_c.join("Program Files"),
|
||||
drive_c.join("Program Files (x86)"),
|
||||
];
|
||||
let already: HashSet<String> = launcher
|
||||
.games
|
||||
.iter()
|
||||
.map(|g| g.exe_path.to_string_lossy().to_lowercase())
|
||||
.collect();
|
||||
let launcher_exe = launcher.exe_path.to_string_lossy().to_lowercase();
|
||||
|
||||
let mut results = Vec::new();
|
||||
let mut seen: HashSet<String> = HashSet::new();
|
||||
for dir in &search_dirs {
|
||||
scan_exe_dir(dir, &drive_c, &launcher_exe, &already, &mut results, &mut seen, 0);
|
||||
}
|
||||
results.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
results
|
||||
}
|
||||
|
||||
fn scan_exe_dir(
|
||||
dir: &Path,
|
||||
drive_c: &Path,
|
||||
launcher_exe: &str,
|
||||
already: &HashSet<String>,
|
||||
out: &mut Vec<(String, String)>,
|
||||
seen: &mut HashSet<String>,
|
||||
depth: u32,
|
||||
) {
|
||||
if depth > 4 {
|
||||
return;
|
||||
}
|
||||
let Ok(entries) = std::fs::read_dir(dir) else { return };
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
let lower = path
|
||||
.file_name()
|
||||
.and_then(|n| n.to_str())
|
||||
.unwrap_or("")
|
||||
.to_lowercase();
|
||||
if SYSTEM_DIRS.iter().any(|s| lower.starts_with(s)) {
|
||||
continue;
|
||||
}
|
||||
if path.is_dir() {
|
||||
scan_exe_dir(&path, drive_c, launcher_exe, already, out, seen, depth + 1);
|
||||
} else if path
|
||||
.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.map(|e| e.eq_ignore_ascii_case("exe"))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let Ok(rel) = path.strip_prefix(drive_c) else { continue };
|
||||
let rel_str = rel.to_string_lossy().to_string();
|
||||
let rel_lower = rel_str.to_lowercase();
|
||||
if rel_lower == launcher_exe || already.contains(&rel_lower) {
|
||||
continue;
|
||||
}
|
||||
if !seen.insert(rel_lower) {
|
||||
continue;
|
||||
}
|
||||
let display = path
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("Unknown")
|
||||
.to_string();
|
||||
out.push((display, rel_str));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_DEPTH: u32 = 3;
|
||||
|
||||
pub fn run(config: &Config, extra_dirs: &[PathBuf], apply: bool) -> Result<()> {
|
||||
|
||||
Reference in New Issue
Block a user