#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] use std::path::PathBuf; use std::process::{Child, Command, Stdio}; use std::sync::Mutex; use tauri::{Emitter, Manager, State, WebviewUrl, WebviewWindowBuilder}; fn debug_log(msg: &str) { if let Ok(user_profile) = std::env::var("USERPROFILE") { let log_path = std::path::Path::new(&user_profile).join("Downloads").join("vyla-rust-debug.log"); if let Ok(mut file) = std::fs::OpenOptions::new().create(true).append(true).open(&log_path) { use std::io::Write; let _ = writeln!(file, "[RUST] {}", msg); } } } struct NodeServices(Mutex>); #[derive(serde::Deserialize, serde::Serialize, Clone)] struct BackendMessage { event: String, data: serde_json::Value, } fn clean_path(path: &std::path::Path) -> String { let s = path.to_string_lossy().to_string(); if s.starts_with("\\\\?\\") { s[4..].to_string() } else { s } } fn find_node_binary(app: &tauri::App) -> Option { if let Ok(resource_dir) = app.path().resource_dir() { let candidates = [ resource_dir.join("node.exe"), resource_dir.join("bin").join("node.exe"), resource_dir.join("_up_").join("node.exe"), ]; for candidate in candidates { if candidate.exists() { return Some(clean_path(&candidate)); } } } if let Ok(path_var) = std::env::var("PATH") { for p in std::env::split_paths(&path_var) { let candidate = p.join("node.exe"); if candidate.exists() { return Some(clean_path(&candidate)); } #[cfg(not(windows))] { let candidate_unix = p.join("node"); if candidate_unix.exists() { return Some(clean_path(&candidate_unix)); } } } } #[cfg(windows)] { let default_paths = [ r"C:\Program Files\nodejs\node.exe", r"C:\Program Files (x86)\nodejs\node.exe", ]; for path in default_paths { let candidate = std::path::Path::new(path); if candidate.exists() { return Some(clean_path(candidate)); } } if let Ok(local_app_data) = std::env::var("LOCALAPPDATA") { let candidate = std::path::PathBuf::from(local_app_data) .join("Programs") .join("nodejs") .join("node.exe"); if candidate.exists() { return Some(clean_path(&candidate)); } } if let Ok(app_data) = std::env::var("APPDATA") { let candidate = std::path::PathBuf::from(app_data) .join("npm") .join("node.exe"); if candidate.exists() { return Some(clean_path(&candidate)); } } } None } fn resolve_script_and_root(app: &tauri::App) -> (String, String) { if let Ok(resource_dir) = app.path().resource_dir() { debug_log(&format!("Resource dir found: {:?}", resource_dir)); let candidates = vec![ resource_dir.join("main.js"), resource_dir.join("_up_").join("main.js"), ]; for candidate in candidates { if candidate.exists() { debug_log(&format!("main.js found at: {:?}", candidate)); let root_dir = candidate.parent().unwrap(); return (clean_path(&candidate), clean_path(root_dir)); } } } let mut dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); if dir.ends_with("src-tauri") { dir.pop(); } let candidate = dir.join("main.js"); debug_log(&format!("Fallback script path: {:?}", candidate)); (clean_path(&candidate), clean_path(&dir)) } const LINK_HANDLER_SCRIPT: &str = r#" window.open = function(url) { if (!url) return null; if (url.startsWith('http://localhost') || url.startsWith('/') || url.startsWith(window.location.origin)) { window.location.href = url; } else if (window.__TAURI__ && window.__TAURI__.opener) { window.__TAURI__.opener.openUrl(url); } else if (window.__TAURI_INTERNALS__) { window.__TAURI_INTERNALS__.invoke('plugin:opener|open_url', { rule: { url: url } }); } return null; }; document.addEventListener('click', (e) => { const a = e.target.closest('a'); if (!a || !a.href) return; const isLocal = a.href.startsWith('http://localhost') || a.href.startsWith('tauri://') || a.href.startsWith(window.location.origin) || a.href.startsWith('/'); if (a.target === '_blank') { e.preventDefault(); if (isLocal) { window.location.href = a.href; } else if (window.__TAURI__ && window.__TAURI__.opener) { window.__TAURI__.opener.openUrl(a.href); } else if (window.__TAURI_INTERNALS__) { window.__TAURI_INTERNALS__.invoke('plugin:opener|open_url', { rule: { url: a.href } }); } } }, true); "#; fn main() { debug_log("=== Vyla Home Executable Started ==="); tauri::Builder::default() .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_process::init()) .plugin(tauri_plugin_opener::init()) .manage(NodeServices(Mutex::new(None))) .setup(|app| { debug_log("Tauri setup started"); let app_data = app.path().app_data_dir().unwrap_or_default(); let app_data_str = clean_path(&app_data); debug_log(&format!("AppData path: {}", app_data_str)); let window = WebviewWindowBuilder::new(app, "main", WebviewUrl::App("index.html".into())) .title("Vyla Home") .inner_size(1280.0, 800.0) .decorations(true) .resizable(true) .initialization_script(LINK_HANDLER_SCRIPT) .build()?; debug_log("Webview created"); let (main_js, root_dir) = resolve_script_and_root(app); let node_bin = match find_node_binary(app) { Some(bin) => bin, None => { debug_log("Node.js runtime not found anywhere"); let _ = window.emit( "error", serde_json::json!({ "message": "Node.js runtime not found on this system. Please install Node.js (https://nodejs.org) and restart Vyla Home." }), ); return Ok(()); } }; debug_log(&format!("Using Node binary: {}", node_bin)); debug_log(&format!("Spawning node with script: {}", main_js)); let mut cmd = Command::new(&node_bin); cmd.arg(&main_js) .current_dir(&root_dir) .env("APPDATA_PATH", &app_data_str) .env("NODE_EXE", &node_bin) .stdout(Stdio::piped()) .stderr(Stdio::piped()); #[cfg(windows)] { use std::os::windows::process::CommandExt; const CREATE_NO_WINDOW: u32 = 0x08000000; cmd.creation_flags(CREATE_NO_WINDOW); } match cmd.spawn() { Ok(mut child) => { debug_log("Node spawned successfully"); if let Some(stdout) = child.stdout.take() { let w = window.clone(); std::thread::spawn(move || { use std::io::{BufRead, BufReader}; let reader = BufReader::new(stdout); for line in reader.lines().flatten() { debug_log(&format!("Node stdout: {}", line)); if let Ok(msg) = serde_json::from_str::(&line) { let _ = w.emit(&msg.event, msg.data.clone()); if msg.event == "ready" { if let Some(url) = msg.data.get("url").and_then(|u| u.as_str()) { let _ = w.eval(&format!("window.location.href = '{}';", url)); } } } } }); } if let Some(stderr) = child.stderr.take() { std::thread::spawn(move || { use std::io::{BufRead, BufReader}; let reader = BufReader::new(stderr); for line in reader.lines().flatten() { debug_log(&format!("Node STDERR: {}", line)); } }); } let state: State = app.state(); if let Ok(mut lock) = state.0.lock() { *lock = Some(child); }; } Err(e) => { debug_log(&format!("FAILED to spawn Node: {}", e)); let _ = window.emit( "error", serde_json::json!({ "message": format!("Failed to launch backend runtime: {}", e) }), ); } } Ok(()) }) .on_window_event(|window, event| { if let tauri::WindowEvent::Destroyed = event { let state: State = window.state(); if let Ok(mut lock) = state.0.lock() { if let Some(mut child) = lock.take() { let _ = child.kill(); } }; } }) .run(tauri::generate_context!()) .expect("error while running tauri application"); }