Files
umutray/src/util.rs
T
funman300 9134d3bab0 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>
2026-04-18 19:50:48 -07:00

64 lines
2.2 KiB
Rust

use iced::futures::channel::oneshot;
/// Run a blocking closure on a thread pool thread and await its result.
/// Used to offload blocking work (HTTP, disk, process spawning) without
/// stalling the iced event loop.
pub async fn async_blocking<T, F>(f: F) -> T
where
T: Send + 'static,
F: FnOnce() -> T + Send + 'static,
{
let (tx, rx) = oneshot::channel();
std::thread::spawn(move || {
let _ = tx.send(f());
});
rx.await.expect("blocking task panicked")
}
/// Open a native folder picker dialog and return the chosen path, or None if
/// the user cancelled. Tries zenity (GNOME/GTK) then kdialog (KDE) in order.
pub fn pick_folder(title: &str) -> Option<String> {
for (cmd, args) in [
("zenity", vec!["--file-selection", "--directory", "--title", title]),
("kdialog", vec!["--getexistingdirectory", "/home", "--title", title]),
] {
let Ok(out) = std::process::Command::new(cmd).args(&args).output() else {
continue;
};
if out.status.success() {
let s = String::from_utf8_lossy(&out.stdout).trim().to_string();
if !s.is_empty() {
return Some(s);
}
}
}
None
}
/// Open a native file picker dialog starting in `start_dir`, or None if
/// the user cancelled. Tries zenity then kdialog.
pub fn pick_file(title: &str, start_dir: &str) -> Option<String> {
// zenity uses --filename with a trailing slash to open a directory
let start_slash = format!("{}/", start_dir.trim_end_matches('/'));
let zenity_args = vec![
"--file-selection",
"--title",
title,
"--filename",
&start_slash,
];
let kdialog_args = vec!["--getopenfilename", start_dir, "--title", title];
for (cmd, args) in [("zenity", zenity_args.as_slice()), ("kdialog", kdialog_args.as_slice())] {
let Ok(out) = std::process::Command::new(cmd).args(args).output() else {
continue;
};
if out.status.success() {
let s = String::from_utf8_lossy(&out.stdout).trim().to_string();
if !s.is_empty() {
return Some(s);
}
}
}
None
}