diff --git a/Cargo.lock b/Cargo.lock index 66395e78f..ad081ba42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -145,6 +145,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "bumpalo" +version = "3.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" + [[package]] name = "bytemuck" version = "1.12.3" @@ -243,6 +249,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + [[package]] name = "clap" version = "3.2.23" @@ -1256,6 +1268,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +[[package]] +name = "js-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "json-patch" version = "0.2.6" @@ -1402,26 +1423,6 @@ dependencies = [ "adler", ] -[[package]] -name = "native-dialog" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab637f328b31bd0855c43bd38a4a4455e74324d9e74e0aac6a803422f43abc6" -dependencies = [ - "block", - "cocoa", - "dirs-next", - "objc", - "objc-foundation", - "objc_id", - "once_cell", - "raw-window-handle 0.4.3", - "thiserror", - "wfd", - "which", - "winapi", -] - [[package]] name = "ndk" version = "0.6.0" @@ -1578,6 +1579,16 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +[[package]] +name = "open" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2078c0039e6a54a0c42c28faa984e115fb4c2d5bf2208f77d1961002df8576f8" +dependencies = [ + "pathdiff", + "windows-sys", +] + [[package]] name = "os_pipe" version = "1.1.1" @@ -1654,6 +1665,12 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + [[package]] name = "percent-encoding" version = "2.2.0" @@ -1980,15 +1997,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "raw-window-handle" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b800beb9b6e7d2df1fe337c9e3d04e3af22a124460fb4c30fcc22c9117cefb41" -dependencies = [ - "cty", -] - [[package]] name = "raw-window-handle" version = "0.5.0" @@ -2053,6 +2061,30 @@ dependencies = [ "winapi", ] +[[package]] +name = "rfd" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0149778bd99b6959285b0933288206090c50e2327f47a9c463bfdbf45c8823ea" +dependencies = [ + "block", + "dispatch", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "lazy_static", + "log", + "objc", + "objc-foundation", + "objc_id", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows 0.37.0", +] + [[package]] name = "rustc_version" version = "0.3.3" @@ -2302,9 +2334,10 @@ dependencies = [ name = "slimevr_ui" version = "0.0.0" dependencies = [ + "cfg_aliases", "clap-verbosity-flag", "log", - "native-dialog", + "open", "pretty_env_logger", "rand 0.8.5", "serde", @@ -2312,7 +2345,10 @@ dependencies = [ "tauri", "tauri-build", "tauri-plugin-window-state", + "tempfile", + "which", "win32job", + "winreg", ] [[package]] @@ -2469,7 +2505,7 @@ dependencies = [ "parking_lot", "paste", "png", - "raw-window-handle 0.5.0", + "raw-window-handle", "scopeguard", "serde", "unicode-segmentation", @@ -2515,8 +2551,9 @@ dependencies = [ "os_pipe", "percent-encoding", "rand 0.8.5", - "raw-window-handle 0.5.0", + "raw-window-handle", "regex", + "rfd", "semver 1.0.14", "serde", "serde_json", @@ -2617,7 +2654,7 @@ dependencies = [ "http", "http-range", "rand 0.8.5", - "raw-window-handle 0.5.0", + "raw-window-handle", "serde", "serde_json", "tauri-utils", @@ -2637,7 +2674,7 @@ dependencies = [ "gtk", "percent-encoding", "rand 0.8.5", - "raw-window-handle 0.5.0", + "raw-window-handle", "tauri-runtime", "tauri-utils", "uuid 1.2.2", @@ -3004,6 +3041,82 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + +[[package]] +name = "web-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webkit2gtk" version = "0.18.2" @@ -3089,16 +3202,6 @@ dependencies = [ "windows-metadata", ] -[[package]] -name = "wfd" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e713040b67aae5bf1a0ae3e1ebba8cc29ab2b90da9aa1bff6e09031a8a41d7a8" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "which" version = "4.3.0" @@ -3164,6 +3267,19 @@ dependencies = [ "windows_x86_64_msvc 0.32.0", ] +[[package]] +name = "windows" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647" +dependencies = [ + "windows_aarch64_msvc 0.37.0", + "windows_i686_gnu 0.37.0", + "windows_i686_msvc 0.37.0", + "windows_x86_64_gnu 0.37.0", + "windows_x86_64_msvc 0.37.0", +] + [[package]] name = "windows" version = "0.39.0" @@ -3237,6 +3353,12 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" +[[package]] +name = "windows_aarch64_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a" + [[package]] name = "windows_aarch64_msvc" version = "0.39.0" @@ -3255,6 +3377,12 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" +[[package]] +name = "windows_i686_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1" + [[package]] name = "windows_i686_gnu" version = "0.39.0" @@ -3273,6 +3401,12 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" +[[package]] +name = "windows_i686_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c" + [[package]] name = "windows_i686_msvc" version = "0.39.0" @@ -3291,6 +3425,12 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" +[[package]] +name = "windows_x86_64_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d" + [[package]] name = "windows_x86_64_gnu" version = "0.39.0" @@ -3315,6 +3455,12 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" +[[package]] +name = "windows_x86_64_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d" + [[package]] name = "windows_x86_64_msvc" version = "0.39.0" @@ -3327,6 +3473,15 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + [[package]] name = "winres" version = "0.1.12" diff --git a/gui/package.json b/gui/package.json index a07b3da74..10f01f1b8 100644 --- a/gui/package.json +++ b/gui/package.json @@ -40,7 +40,8 @@ "lint": "eslint src/**/*.{js,jsx,ts,tsx,json}", "lint:fix": "eslint --fix src/**/*.{js,jsx,ts,tsx,json}", "format": "prettier --write src/**/*.{js,jsx,ts,tsx,css,md,json} --config ./.prettierrc", - "preview-vite": "vite preview" + "preview-vite": "vite preview", + "javaversion-build": "javac src-tauri/src/JavaVersion.java" }, "eslintConfig": { "extends": [ diff --git a/gui/src-tauri/Cargo.toml b/gui/src-tauri/Cargo.toml index 2bc2b7d9c..cc1ac6ef4 100644 --- a/gui/src-tauri/Cargo.toml +++ b/gui/src-tauri/Cargo.toml @@ -22,17 +22,36 @@ custom-protocol = ["tauri/custom-protocol"] [build-dependencies] tauri-build = { version = "1.2", features = [] } +cfg_aliases = "0.1" [dependencies] serde_json = "1" serde = { version = "1", features = ["derive"] } -tauri = { version = "1.2", features = ["cli", "devtools", "fs-all", "path-all", "shell-execute", "window-close", "window-maximize", "window-minimize", "window-set-resizable", "window-set-title", "window-start-dragging", "window-unmaximize", "window-unminimize"] } +tauri = { version = "1.2", features = [ + "cli", + "devtools", + "dialog", + "fs-all", + "path-all", + "shell-execute", + "window-close", + "window-maximize", + "window-minimize", + "window-set-resizable", + "window-set-title", + "window-start-dragging", + "window-unmaximize", + "window-unminimize" +] } pretty_env_logger = "0.4" log = "0.4" clap-verbosity-flag = "1" tauri-plugin-window-state = "0.1.0" -native-dialog = "0.6.3" rand = "0.8.5" +tempfile = "3" +which = "4.3.0" +open = "3" [target.'cfg(windows)'.dependencies] win32job = "1" +winreg = "0.10.1" diff --git a/gui/src-tauri/build.rs b/gui/src-tauri/build.rs index c1ea37328..1ca8a194b 100644 --- a/gui/src-tauri/build.rs +++ b/gui/src-tauri/build.rs @@ -1,3 +1,9 @@ +use cfg_aliases::cfg_aliases; + fn main() { - tauri_build::build() + tauri_build::build(); + cfg_aliases! { + mobile: { any(target_os = "ios", target_os = "android") }, + desktop: { not(any(target_os = "ios", target_os = "android")) } + } } diff --git a/gui/src-tauri/src/JavaVersion.class b/gui/src-tauri/src/JavaVersion.class new file mode 100644 index 000000000..046a27ae1 Binary files /dev/null and b/gui/src-tauri/src/JavaVersion.class differ diff --git a/gui/src-tauri/src/JavaVersion.java b/gui/src-tauri/src/JavaVersion.java new file mode 100644 index 000000000..29367f25d --- /dev/null +++ b/gui/src-tauri/src/JavaVersion.java @@ -0,0 +1,8 @@ +public class JavaVersion { + + public static void main (String[] args) + { + var version = Runtime.version().version().get(0); + System.exit(version); + } +} diff --git a/gui/src-tauri/src/main.rs b/gui/src-tauri/src/main.rs index 70007e005..d6b2acd46 100644 --- a/gui/src-tauri/src/main.rs +++ b/gui/src-tauri/src/main.rs @@ -1,20 +1,22 @@ -#![cfg_attr( - all(not(debug_assertions), windows), - windows_subsystem = "windows" -)] +#![cfg_attr(all(not(debug_assertions), windows), windows_subsystem = "windows")] use std::env; +use std::ffi::{OsStr, OsString}; +use std::io::Write; use std::panic; use std::path::PathBuf; +use std::process::{Child, Stdio}; +use std::str::FromStr; use clap::Parser; use clap_verbosity_flag::{InfoLevel, Verbosity}; -use native_dialog::{MessageDialog, MessageType}; -use rand::seq::SliceRandom; -use rand::thread_rng; -use tauri::api::clap; -use tauri::api::process::Command; +use rand::{seq::SliceRandom, thread_rng}; +use tauri::api::{clap, process::Command}; use tauri::Manager; +use tempfile::Builder; +use which::which_all; +/// It's an i32 because we check it through exit codes of the process +const MINIMUM_JAVA_VERSION: i32 = 17; static POSSIBLE_TITLES: &[&str] = &[ "Panicking situation", "looking for spatula", @@ -35,47 +37,68 @@ struct Cli { } fn is_valid_path(path: &PathBuf) -> bool { - let java_folder = path.join("jre"); + // Might need to be changed in the future, at least for linux let server_path = path.join("slimevr.jar"); - return java_folder.exists() && server_path.exists(); + return server_path.exists(); } fn get_launch_path(cli: Cli) -> Option { let mut path = cli.launch_from_path.unwrap_or_default(); - if path.exists() && is_valid_path(&path) { + if is_valid_path(&path) { return Some(path); } path = env::current_dir().unwrap(); - if path.exists() && is_valid_path(&path) { + if is_valid_path(&path) { return Some(path); } path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - if path.exists() && is_valid_path(&path) { + if is_valid_path(&path) { return Some(path); } None } -fn show_error(text: &str) { - MessageDialog::new() - .set_title(&format!( +fn spawn_java(java: &OsStr, java_version: &OsStr) -> std::io::Result { + std::process::Command::new(java) + .arg(java_version) + .stdin(Stdio::null()) + .stderr(Stdio::null()) + .stdout(Stdio::null()) + .spawn() +} + +#[cfg(desktop)] +fn show_error(text: &str) -> bool { + use tauri::api::dialog::{ + blocking::MessageDialogBuilder, MessageDialogButtons, MessageDialogKind, + }; + + MessageDialogBuilder::new( + format!( "SlimeVR GUI crashed - {}", POSSIBLE_TITLES.choose(&mut thread_rng()).unwrap() - )) - .set_text(text) - .set_type(MessageType::Error) - .show_alert() - .unwrap(); + ), + text, + ) + .buttons(MessageDialogButtons::Ok) + .kind(MessageDialogKind::Error) + .show() +} + +#[cfg(mobile)] +fn show_error(text: &str) -> bool { + // needs to do native stuff on mobile + false } fn main() { // Make an error dialog box when panicking panic::set_hook(Box::new(|panic_info| { - eprintln!("{}", panic_info); + println!("{}", panic_info); show_error(&panic_info.to_string()); })); @@ -90,6 +113,7 @@ fn main() { } // Ensure child processes die when spawned on windows + // and then check for WebView2's existence #[cfg(windows)] { use win32job::{ExtendedLimitInfo, Job}; @@ -103,6 +127,23 @@ fn main() { // We don't do anything with the job anymore, but we shouldn't drop it because that would // terminate our process tree. So we intentionally leak it instead. std::mem::forget(job); + + if !webview2_exists() { + // This makes a dialog appear which let's you press Ok or Cancel + // If you press Ok it will open the SlimeVR installer documentation + use tauri::api::dialog::{ + blocking::MessageDialogBuilder, MessageDialogButtons, MessageDialogKind, + }; + + let confirm = MessageDialogBuilder::new("SlimeVR", "Couldn't find WebView2 installed. You can install it with the SlimeVR installer") + .buttons(MessageDialogButtons::OkCancel) + .kind(MessageDialogKind::Error) + .show(); + if confirm { + open::that("https://docs.slimevr.dev/server-setup/installing-and-connecting.html#install-the-latest-slimevr-installer").unwrap(); + } + return; + } } // Spawn server process @@ -111,13 +152,22 @@ fn main() { let stdout_recv = if let Some(p) = run_path { log::info!("Server found on path: {}", p.to_str().unwrap()); - let java_folder = p.join("jre"); - let (recv, _child) = - Command::new(java_folder.join("bin/java").to_str().unwrap()) - .current_dir(p) - .args(["-Xmx512M", "-jar", "slimevr.jar", "--no-gui"]) - .spawn() - .expect("Unable to start the server jar"); + // Check if any Java already installed is compatible + let jre = p.join("jre/bin/java"); + let java_bin = jre + .exists() + .then(|| jre.into_os_string()) + .or_else(|| valid_java_paths().first().map(|x| x.0.to_owned())); + if let None = java_bin { + show_error(&format!("Couldn't find a compatible Java version, please download Java {} or higher", MINIMUM_JAVA_VERSION)); + return; + }; + + let (recv, _child) = Command::new(java_bin.unwrap().to_string_lossy()) + .current_dir(p) + .args(["-Xmx512M", "-jar", "slimevr.jar", "--no-gui"]) + .spawn() + .expect("Unable to start the server jar"); Some(recv) } else { log::warn!("No server found. We will not start the server."); @@ -157,3 +207,82 @@ fn main() { .run(tauri::generate_context!()) .expect("error while running tauri application"); } + +#[cfg(windows)] +/// Check if WebView2 exists +fn webview2_exists() -> bool { + use winreg::enums::*; + use winreg::RegKey; + + // First on the machine itself + let machine: Option = RegKey::predef(HKEY_LOCAL_MACHINE) + .open_subkey(r"SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}") + .map(|r| r.get_value("pv").ok()).ok().flatten(); + let mut exists = false; + if let Some(version) = machine { + exists = version.split('.').any(|x| x != "0"); + } + // Then in the current user + if !exists { + let user: Option = RegKey::predef(HKEY_CURRENT_USER) + .open_subkey( + r"Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}", + ) + .map(|r| r.get_value("pv").ok()) + .ok() + .flatten(); + if let Some(version) = user { + exists = version.split('.').any(|x| x != "0"); + } + } + exists +} + +fn valid_java_paths() -> Vec<(OsString, i32)> { + let mut file = Builder::new() + .suffix(".class") + .tempfile() + .expect("Couldn't generate .class file"); + file.write_all(include_bytes!("JavaVersion.class")) + .expect("Couldn't write to .class file"); + let java_version = file.into_temp_path(); + + // Check if main Java is a supported version + let main_java = if let Ok(java_home) = std::env::var("JAVA_HOME") { + PathBuf::from(java_home).join("bin/java").into_os_string() + } else { + OsString::from_str("java").unwrap() + }; + if let Some(main_child) = spawn_java(&main_java, java_version.as_os_str()) + .expect("Couldn't spawn the main Java binary") + .wait() + .expect("Couldn't execute the main Java binary") + .code() + { + if main_child >= MINIMUM_JAVA_VERSION { + return vec![(main_java, main_child)]; + } + } + + // Otherwise check if anything else is a supported version + let mut childs = vec![]; + for java in which_all("java").unwrap() { + let res = spawn_java(java.as_os_str(), java_version.as_os_str()); + + match res { + Ok(child) => childs.push((java.into_os_string(), child)), + Err(e) => println!("Error on trying to spawn a Java executable: {}", e), + } + } + + childs + .into_iter() + .filter_map(|(p, mut c)| { + c.wait() + .expect("Failed on executing a Java executable") + .code() + .map(|code| (p, code)) + .filter(|(_p, code)| *code >= MINIMUM_JAVA_VERSION) + }) + .collect() +}