mirror of
https://gitlab.com/vyla-entertainment/home.git
synced 2026-11-13 13:47:21 -05:00
278 lines
10 KiB
Rust
278 lines
10 KiB
Rust
#![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<Option<Child>>);
|
|
|
|
#[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<String> {
|
|
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::<BackendMessage>(&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<NodeServices> = 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<NodeServices> = 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");
|
|
} |