From 8506b294893df2e3c1e8af2f6373559ad56cf1d2 Mon Sep 17 00:00:00 2001 From: alwaysmesmerizingyou Date: Sat, 22 Aug 2026 16:04:55 -0700 Subject: [PATCH] changed debug logs, made player page open in new tab, made title bar transparent, fixed runtime issues --- DOCUEMENTATION/CLEAR.md | 7 +- DOCUEMENTATION/FORDEV.md | 19 +++ DOCUEMENTATION/LOGS.md | 6 + main.js | 28 +++- package.json | 4 +- release.js | 5 +- src-tauri/Cargo.lock | 1 + src-tauri/Cargo.toml | 1 + src-tauri/src/main.rs | 351 +++++++++++++++++++++++++++++++-------- 9 files changed, 346 insertions(+), 76 deletions(-) create mode 100644 DOCUEMENTATION/FORDEV.md create mode 100644 DOCUEMENTATION/LOGS.md diff --git a/DOCUEMENTATION/CLEAR.md b/DOCUEMENTATION/CLEAR.md index cbd335a..c23ebe4 100644 --- a/DOCUEMENTATION/CLEAR.md +++ b/DOCUEMENTATION/CLEAR.md @@ -1,6 +1,10 @@ +How to clear this project: + +```powershell taskkill /F /IM "node.exe" 2>$null taskkill /F /IM "vyla-home.exe" 2>$null taskkill /F /IM "Vyla Home.exe" 2>$null +taskkill /F /IM node.exe -ErrorAction SilentlyContinue Stop-Process -Name "node", "vyla-home", "Vyla Home" -Force -ErrorAction SilentlyContinue @@ -37,4 +41,5 @@ foreach ($path in $pathsToDelete) { if (Test-Path $path) { Remove-Item -Recurse -Force $path -ErrorAction SilentlyContinue } -} \ No newline at end of file +} +``` \ No newline at end of file diff --git a/DOCUEMENTATION/FORDEV.md b/DOCUEMENTATION/FORDEV.md new file mode 100644 index 0000000..925328f --- /dev/null +++ b/DOCUEMENTATION/FORDEV.md @@ -0,0 +1,19 @@ +How to start this without releasing it. + +Package everything into a local payload zip: + +```powershell +npm run build:payload +``` + +Start the Tauri app in development mode: + +```powershell +npm run dev +``` + +To release it, run: + +```powershell +npm run release:full +``` \ No newline at end of file diff --git a/DOCUEMENTATION/LOGS.md b/DOCUEMENTATION/LOGS.md new file mode 100644 index 0000000..3bb15d2 --- /dev/null +++ b/DOCUEMENTATION/LOGS.md @@ -0,0 +1,6 @@ +Logs of node and rust can be found here: + +%APPDATA%\vyla-home\vyla-home-files\logs + +**Full path:** +C:\Users\\AppData\Roaming\vyla-home\vyla-home-files\logs\ \ No newline at end of file diff --git a/main.js b/main.js index 626c24a..a7be3f0 100644 --- a/main.js +++ b/main.js @@ -6,7 +6,11 @@ const ServiceManager = require('./service-manager'); const Patcher = require('./patcher'); const UpdateClient = require('./update-client'); -const debugLogPath = path.join(os.homedir(), 'Downloads', 'vyla-node-debug.log'); +const APPDATA_DIR = process.env.APPDATA_PATH || path.join(os.homedir(), 'AppData', 'Roaming', 'vyla-home', 'vyla-home-files'); +const LOGS_DIR = path.join(APPDATA_DIR, 'logs'); +try { fs.mkdirSync(LOGS_DIR, { recursive: true }); } catch (e) { } + +const debugLogPath = path.join(LOGS_DIR, 'node-debug.log'); function debugLog(msg) { try { fs.appendFileSync(debugLogPath, `[NODE] ${msg}\n`, 'utf8'); } catch (e) { } } @@ -19,8 +23,6 @@ let expressServer; let playerExpressApp; let playerExpressServer; -const APPDATA_DIR = process.env.APPDATA_PATH || path.join(os.homedir(), 'AppData', 'Roaming', 'vyla-home', 'vyla-home-files'); -fs.mkdirSync(APPDATA_DIR, { recursive: true }); debugLog(`AppData Dir: ${APPDATA_DIR}`); const envConfig = JSON.parse(fs.readFileSync(path.join(__dirname, 'config/env.json'), 'utf8')); @@ -192,6 +194,16 @@ async function start() { const playerDir = path.join(APPDATA_DIR, 'player'); playerExpressApp = express(); + playerExpressApp.use((req, res, next) => { + res.header('Access-Control-Allow-Origin', '*'); + res.header('Access-Control-Allow-Methods', 'GET,POST,OPTIONS'); + if (req.method === 'OPTIONS') return res.sendStatus(200); + next(); + }); + playerExpressApp.get('/__tauri_cmd/:cmd', (req, res) => { + sendToTauri('tauri-cmd', { cmd: req.params.cmd, url: req.query.url, window: req.query.window }); + res.sendStatus(200); + }); playerExpressApp.use(express.static(playerDir, { maxAge: '1d', etag: true })); playerExpressApp.get('*', (req, res) => { res.sendFile(path.join(playerDir, 'index.html')); }); playerExpressServer = playerExpressApp.listen(3001, '0.0.0.0'); @@ -203,6 +215,16 @@ async function start() { const frontendDir = path.join(APPDATA_DIR, 'frontend'); expressApp = express(); + expressApp.use((req, res, next) => { + res.header('Access-Control-Allow-Origin', '*'); + res.header('Access-Control-Allow-Methods', 'GET,POST,OPTIONS'); + if (req.method === 'OPTIONS') return res.sendStatus(200); + next(); + }); + expressApp.get('/__tauri_cmd/:cmd', (req, res) => { + sendToTauri('tauri-cmd', { cmd: req.params.cmd, url: req.query.url, window: req.query.window }); + res.sendStatus(200); + }); expressApp.use(express.static(frontendDir, { maxAge: '1d', etag: true })); expressApp.get('*', (req, res) => { res.sendFile(path.join(frontendDir, 'index.html')); }); diff --git a/package.json b/package.json index 1a8100f..d2a87e6 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "dev": "tauri dev", "build:payload": "node build-pipeline.js", - "build": "tauri build", + "build": "node -e \"require('child_process').execSync('tauri build', {stdio:'inherit', env:Object.assign({}, process.env, {WEBVIEW2_STATIC_LIB:'1'})})\"", "release": "node release.js", "release:full": "node release.js --installer" }, @@ -54,4 +54,4 @@ "installerHeaderIcon": "build/icon.ico" } } -} +} \ No newline at end of file diff --git a/release.js b/release.js index 4d6b978..24e4219 100644 --- a/release.js +++ b/release.js @@ -157,7 +157,10 @@ async function main() { await ensureBundledNode(); console.log('[Optional] Compiling Windows Tauri installer locally...'); - execSync('npm run build', { stdio: 'inherit' }); + execSync('npm run build', { + stdio: 'inherit', + env: Object.assign({}, process.env, { WEBVIEW2_STATIC_LIB: '1' }) + }); const nsisDir = path.join(__dirname, 'src-tauri/target/release/bundle/nsis'); if (fs.existsSync(nsisDir)) { diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index de75677..3f4deec 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -4231,6 +4231,7 @@ dependencies = [ "tauri-plugin-opener", "tauri-plugin-process", "tauri-plugin-shell", + "url", ] [[package]] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index eba2c07..e9fe32d 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -14,3 +14,4 @@ tauri-plugin-process = "2" tauri-plugin-shell = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" +url = "2" diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 4a0dba9..f0ecf9b 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -6,12 +6,20 @@ 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); - } + let log_dir = if let Ok(app_data) = std::env::var("APPDATA") { + std::path::PathBuf::from(app_data).join("vyla-home").join("vyla-home-files").join("logs") + } else if let Ok(user_profile) = std::env::var("USERPROFILE") { + std::path::PathBuf::from(user_profile).join(".vyla-home").join("logs") + } else { + std::path::PathBuf::from("logs") + }; + + let _ = std::fs::create_dir_all(&log_dir); + let log_path = log_dir.join("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); } } @@ -101,8 +109,6 @@ fn find_node_binary(app: &tauri::App) -> Option { 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"), @@ -110,7 +116,6 @@ fn resolve_script_and_root(app: &tauri::App) -> (String, String) { 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)); } @@ -122,20 +127,42 @@ fn resolve_script_and_root(app: &tauri::App) -> (String, String) { 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 } }); + function doInvoke(cmd, args) { + let baseUrl = window.location.protocol.startsWith('http') ? window.location.origin : 'http://127.0.0.1:4890'; + let label = window.__VYLA_WINDOW_LABEL || 'main'; + let url = `${baseUrl}/__tauri_cmd/${cmd}?window=${label}`; + if (args && args.url) { + url += `&url=${encodeURIComponent(args.url)}`; } + fetch(url).catch(err => { + const inv = window.__TAURI_INTERNALS__?.invoke || window.__TAURI__?.core?.invoke || window.__TAURI__?.invoke; + if (inv) inv(cmd, args || {}).catch(e => console.error(e)); + }); + } + + function minimizeWindow() { + doInvoke('win_minimize'); + } + + function maximizeWindow() { + doInvoke('win_maximize'); + } + + function closeWindow() { + doInvoke('win_close'); + } + + function dragWindow() { + doInvoke('win_drag'); + } + + window.open = function(url, target, features) { + if (!url) return null; + doInvoke('open_new_window', { url: url }); return null; }; @@ -143,21 +170,197 @@ const LINK_HANDLER_SCRIPT: &str = r#" 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') { + if (a.target === '_blank' || e.ctrlKey || e.metaKey || e.button === 1) { 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 } }); - } + e.stopPropagation(); + doInvoke('open_new_window', { url: a.href }); } }, true); + + function injectTitlebar() { + if (document.getElementById('tauri-custom-titlebar')) { + return; + } + if (!document.body) { + return; + } + + const style = document.createElement('style'); + style.innerHTML = ` + #tauri-custom-titlebar { + position: fixed !important; top: 0 !important; left: 0 !important; right: 0 !important; + height: 32px !important; display: flex !important; justify-content: space-between !important; + z-index: 2147483647 !important; background: transparent !important; + pointer-events: none !important; box-sizing: border-box !important; margin: 0 !important; padding: 0 !important; + } + .tauri-drag-region { + flex: 1 !important; height: 100% !important; cursor: default !important; pointer-events: auto !important; + } + .tauri-window-controls { + display: flex !important; pointer-events: auto !important; height: 32px !important; margin: 0 !important; padding: 0 !important; + position: relative !important; z-index: 2147483648 !important; + } + button.tauri-btn { + width: 46px !important; max-width: 46px !important; min-width: 46px !important; + height: 32px !important; max-height: 32px !important; min-height: 32px !important; + background: transparent !important; border: none !important; + color: #fff !important; display: flex !important; align-items: center !important; + justify-content: center !important; cursor: pointer !important; + outline: none !important; padding: 0 !important; margin: 0 !important; + border-radius: 0 !important; box-shadow: none !important; + appearance: none !important; opacity: 1 !important; transform: none !important; + position: relative !important; z-index: 2147483649 !important; + } + button.tauri-btn:hover { background: rgba(255, 255, 255, 0.15) !important; } + button#tauri-btn-close:hover { background: #E81123 !important; color: white !important; } + button.tauri-btn svg { width: 10px !important; height: 10px !important; fill: currentColor !important; display: block !important; } + `; + document.head.appendChild(style); + + const tb = document.createElement('div'); + tb.id = 'tauri-custom-titlebar'; + tb.innerHTML = ` +
+
+ + + +
+ `; + + document.body.appendChild(tb); + + document.addEventListener('click', (e) => { + const btn = e.target.closest('.tauri-btn'); + if (!btn) return; + + const action = btn.getAttribute('data-action'); + + e.preventDefault(); + e.stopPropagation(); + e.stopImmediatePropagation(); + + if (action === 'minimize') { + minimizeWindow(); + } else if (action === 'maximize') { + maximizeWindow(); + } else if (action === 'close') { + closeWindow(); + } + }, true); + + const dragRegion = tb.querySelector('.tauri-drag-region'); + if (dragRegion) { + dragRegion.addEventListener('mousedown', (e) => { + if (e.buttons === 1) { + e.preventDefault(); + e.stopPropagation(); + dragWindow(); + } + }); + } + } + + injectTitlebar(); + + setInterval(injectTitlebar, 1000); + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', injectTitlebar); + } else { + injectTitlebar(); + } + + window.addEventListener('load', injectTitlebar); "#; +#[tauri::command] +fn win_minimize(window: tauri::Window) -> Result<(), String> { + debug_log("win_minimize command called"); + window.minimize().map_err(|e| { + debug_log(&format!("win_minimize error: {}", e)); + e.to_string() + }) +} + +#[tauri::command] +fn win_maximize(window: tauri::Window) -> Result<(), String> { + debug_log("win_maximize command called"); + if let Ok(is_max) = window.is_maximized() { + if is_max { + debug_log("Window is maximized, unmaximizing"); + window.unmaximize().map_err(|e| { + debug_log(&format!("win_unmaximize error: {}", e)); + e.to_string() + }) + } else { + debug_log("Window is not maximized, maximizing"); + window.maximize().map_err(|e| { + debug_log(&format!("win_maximize error: {}", e)); + e.to_string() + }) + } + } else { + debug_log("Could not determine maximized state"); + Ok(()) + } +} + +#[tauri::command] +fn win_close(window: tauri::Window) -> Result<(), String> { + debug_log("win_close command called"); + window.close().map_err(|e| { + debug_log(&format!("win_close error: {}", e)); + e.to_string() + }) +} + +#[tauri::command] +fn win_drag(window: tauri::Window) -> Result<(), String> { + debug_log("win_drag command called"); + window.start_dragging().map_err(|e| { + debug_log(&format!("win_drag error: {}", e)); + e.to_string() + }) +} + +#[tauri::command] +fn open_new_window(app: tauri::AppHandle, url: String) -> Result<(), String> { + debug_log(&format!("Requested to open new window for: {}", url)); + + let label = format!("win_{}", std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis()); + + let final_url = if url.starts_with("http") || url.starts_with("tauri://") { + url.clone() + } else if url.starts_with('/') { + format!("http://localhost:4890{}", url) + } else { + format!("http://localhost:4890/{}", url) + }; + + let parsed_url = url::Url::parse(&final_url) + .unwrap_or_else(|_| url::Url::parse("http://localhost:4890").unwrap()); + + match tauri::WebviewWindowBuilder::new(&app, label.clone(), tauri::WebviewUrl::External(parsed_url)) + .title("Vyla Home") + .inner_size(1280.0, 800.0) + .decorations(false) + .transparent(true) + .initialization_script(&format!("window.__VYLA_WINDOW_LABEL = '{}';", label)) + .initialization_script(LINK_HANDLER_SCRIPT) + .build() + { + Ok(_) => { + debug_log("Successfully spawned new window"); + Ok(()) + }, + Err(e) => { + debug_log(&format!("ERROR: Failed to create new window: {}", e)); + Err(e.to_string()) + } + } +} + fn main() { debug_log("=== Vyla Home Executable Started ==="); @@ -165,41 +368,43 @@ fn main() { .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_process::init()) .plugin(tauri_plugin_opener::init()) + .invoke_handler(tauri::generate_handler![ + win_minimize, + win_maximize, + win_close, + win_drag, + open_new_window + ]) .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())) + + let window = WebviewWindowBuilder::new(app, "main", WebviewUrl::App("index.html".into())) .title("Vyla Home") .inner_size(1280.0, 800.0) - .decorations(true) + .decorations(false) + .transparent(true) .resizable(true) + .initialization_script("window.__VYLA_WINDOW_LABEL = 'main';") .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." + "message": "Node.js runtime not found. Please ensure Node.js is installed or bundled correctly." }), ); 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) @@ -217,48 +422,54 @@ fn main() { 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)); + for line in reader.lines().flatten() { 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 msg.event == "tauri-cmd" { + let window_label = msg.data.get("window").and_then(|w| w.as_str()).unwrap_or("main"); + if let Some(cmd_window) = w.app_handle().get_webview_window(window_label) { + if let Some(cmd) = msg.data.get("cmd").and_then(|c| c.as_str()) { + match cmd { + "win_minimize" => { let _ = cmd_window.minimize(); }, + "win_maximize" => { + if let Ok(true) = cmd_window.is_maximized() { let _ = cmd_window.unmaximize(); } + else { let _ = cmd_window.maximize(); } + }, + "win_close" => { let _ = cmd_window.close(); }, + "win_drag" => { let _ = cmd_window.start_dragging(); }, + "open_new_window" => { + if let Some(url) = msg.data.get("url").and_then(|u| u.as_str()) { + let _ = open_new_window(w.app_handle().clone(), url.to_string()); + } + }, + _ => {} + } + } + } + } else { + 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) - }), - ); + let _ = window.emit("error", serde_json::json!({ "message": format!("Failed to launch backend runtime: {}", e) })); } } @@ -266,12 +477,14 @@ fn main() { }) .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(); - } - }; + if window.label() == "main" { + 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!())