diff --git a/Cargo.lock b/Cargo.lock index b1fd8b1..ce49ccd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1576,6 +1576,7 @@ dependencies = [ "log", "notify", "object", + "path-slash", "ppc750cl", "rabbitizer", "rfd", @@ -1583,6 +1584,7 @@ dependencies = [ "thiserror", "tracing-subscriber", "tracing-wasm", + "winapi", ] [[package]] @@ -1660,6 +1662,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "path-slash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" + [[package]] name = "peeking_take_while" version = "0.1.2" diff --git a/Cargo.toml b/Cargo.toml index 191afbe..3e83d18 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,10 @@ egui_extras = "0.19.0" ppc750cl = { git = "https://github.com/terorie/ppc750cl" } rabbitizer = { git = "https://github.com/encounter/rabbitizer-rs", rev = "10c279b2ef251c62885b1dcdcfe740b0db8e9956" } +[target.'cfg(windows)'.dependencies] +path-slash = "0.2.0" +winapi = "0.3.9" + # native: [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tracing-subscriber = "0.3" diff --git a/src/app.rs b/src/app.rs index 6e9f7d9..dec373f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -61,6 +61,11 @@ pub struct ViewState { #[derive(Default, Clone, serde::Deserialize, serde::Serialize)] #[serde(default)] pub struct AppConfig { + pub custom_make: String, + // WSL2 settings + #[serde(skip)] + pub available_wsl_distros: Option>, + pub selected_wsl_distro: Option, // Split obj pub project_dir: Option, pub build_asm_dir: Option, @@ -156,7 +161,6 @@ impl eframe::App for App { }); } else { egui::SidePanel::left("side_panel").show(ctx, |ui| { - ui.heading("Config"); config_ui(ui, config, view_state); jobs_ui(ui, view_state); }); @@ -194,7 +198,9 @@ impl eframe::App for App { ui.separator(); }); - if view_state.jobs.iter().any(|job| { + // Windows + request_repaint_after breaks dialogs: + // https://github.com/emilk/egui/issues/2003 + if cfg!(windows) || view_state.jobs.iter().any(|job| { if let Some(handle) = &job.handle { return !handle.is_finished(); } diff --git a/src/jobs/build.rs b/src/jobs/build.rs index b4f3f62..f978932 100644 --- a/src/jobs/build.rs +++ b/src/jobs/build.rs @@ -2,7 +2,7 @@ use std::{ path::Path, process::Command, str::from_utf8, - sync::{mpsc::Receiver, Arc, RwLock}, + sync::{Arc, mpsc::Receiver, RwLock}, }; use anyhow::{Context, Error, Result}; @@ -11,7 +11,7 @@ use crate::{ app::AppConfig, diff::diff_objs, elf, - jobs::{queue_job, update_status, Job, JobResult, JobState, Status}, + jobs::{Job, JobResult, JobState, queue_job, Status, update_status}, obj::ObjInfo, }; @@ -26,13 +26,36 @@ pub struct BuildResult { pub second_obj: Option, } -fn run_make(cwd: &Path, arg: &Path) -> BuildStatus { +fn run_make(cwd: &Path, arg: &Path, config: &AppConfig) -> BuildStatus { match (|| -> Result { - let output = Command::new("make") - .current_dir(cwd) - .arg(arg) - .output() - .context("Failed to execute build")?; + let make = if config.custom_make.is_empty() { "make" } else { &config.custom_make }; + #[cfg(not(windows))] + let mut command = Command::new(make).current_dir(cwd).arg(arg); + #[cfg(windows)] + let mut command = { + use path_slash::PathExt; + use std::os::windows::process::CommandExt; + let mut command = if config.selected_wsl_distro.is_some() { + Command::new("wsl") + } else { + Command::new(make) + }; + if let Some(distro) = &config.selected_wsl_distro { + command + .arg("--cd") + .arg(cwd) + .arg("-d") + .arg(distro) + .arg("--") + .arg(make) + .arg(arg.to_slash_lossy().as_ref()); + } else { + command.current_dir(cwd).arg(arg); + } + command.creation_flags(winapi::um::winbase::CREATE_NO_WINDOW); + command + }; + let output = command.output().context("Failed to execute build")?; let stdout = from_utf8(&output.stdout).context("Failed to process stdout")?; let stderr = from_utf8(&output.stderr).context("Failed to process stderr")?; Ok(BuildStatus { @@ -72,10 +95,10 @@ fn run_build( src_path.strip_prefix(project_dir).context("Failed to create relative src obj path")?; update_status(status, format!("Building asm {}", obj_path), 0, 5, &cancel)?; - let first_status = run_make(project_dir, asm_path_rel); + let first_status = run_make(project_dir, asm_path_rel, &config); update_status(status, format!("Building src {}", obj_path), 1, 5, &cancel)?; - let second_status = run_make(project_dir, src_path_rel); + let second_status = run_make(project_dir, src_path_rel, &config); let mut first_obj = if first_status.success { update_status(status, format!("Loading asm {}", obj_path), 2, 5, &cancel)?; diff --git a/src/views/config.rs b/src/views/config.rs index 32019a5..d5e566a 100644 --- a/src/views/config.rs +++ b/src/views/config.rs @@ -1,13 +1,53 @@ +use std::process::Command; +use std::string::FromUtf16Error; use std::sync::{Arc, RwLock}; +use anyhow::{Context, Result}; + use crate::{ app::{AppConfig, DiffKind, ViewState}, jobs::{bindiff::queue_bindiff, build::queue_build}, }; +#[cfg(windows)] +fn process_utf16(bytes: &[u8]) -> Result { + let u16_bytes: Vec = bytes + .chunks_exact(2) + .filter_map(|c| Some(u16::from_ne_bytes(c.try_into().ok()?))) + .collect(); + String::from_utf16(&u16_bytes) +} + +#[cfg(windows)] +fn wsl_cmd(args: &[&str]) -> Result { + use std::os::windows::process::CommandExt; + let output = Command::new("wsl") + .args(args) + .creation_flags(winapi::um::winbase::CREATE_NO_WINDOW) + .output() + .context("Failed to execute wsl")?; + process_utf16(&output.stdout).context("Failed to process stdout") +} + +#[cfg(windows)] +fn fetch_wsl2_distros() -> Vec { + wsl_cmd(&["-l", "-q"]) + .map(|stdout| { + stdout + .split('\n') + .filter(|s| !s.trim().is_empty()) + .map(|s| s.trim().to_string()) + .collect() + }) + .unwrap_or_default() +} + pub fn config_ui(ui: &mut egui::Ui, config: &Arc>, view_state: &mut ViewState) { let mut config_guard = config.write().unwrap(); let AppConfig { + custom_make, + available_wsl_distros, + selected_wsl_distro, project_dir, project_dir_change, build_asm_dir, @@ -17,6 +57,30 @@ pub fn config_ui(ui: &mut egui::Ui, config: &Arc>, view_state: right_obj, } = &mut *config_guard; + ui.heading("Build config"); + + #[cfg(windows)] + { + if available_wsl_distros.is_none() { + *available_wsl_distros = Some(fetch_wsl2_distros()); + } + egui::ComboBox::from_label("Run in WSL2") + .selected_text(selected_wsl_distro.as_ref().unwrap_or(&"None".to_string())) + .show_ui(ui, |ui| { + ui.selectable_value(selected_wsl_distro, None, "None"); + for distro in available_wsl_distros.as_ref().unwrap() { + ui.selectable_value(selected_wsl_distro, Some(distro.clone()), distro); + } + }); + } + + ui.label("Custom make program:"); + ui.text_edit_singleline(custom_make); + + ui.separator(); + + ui.heading("Project config"); + if view_state.diff_kind == DiffKind::SplitObj { if ui.button("Select project dir").clicked() { if let Some(path) = rfd::FileDialog::new().pick_folder() {