diff --git a/DOCUEMENTATION/FORDEV.md b/DOCUEMENTATION/FORDEV.md index 925328f..21e9d7f 100644 --- a/DOCUEMENTATION/FORDEV.md +++ b/DOCUEMENTATION/FORDEV.md @@ -16,4 +16,10 @@ To release it, run: ```powershell npm run release:full +``` + +Fixing os error 32 + +```powershell +Stop-Process -Name "vyla-home", "node", "cargo" -Force -ErrorAction SilentlyContinue ``` \ No newline at end of file diff --git a/main.js b/main.js index dfc3666..a444bd8 100644 --- a/main.js +++ b/main.js @@ -102,7 +102,8 @@ const updateClient = new UpdateClient(APPDATA_DIR); async function start() { debugLog("Inside start() function"); - sendToTauri('status', { message: 'Initializing patcher...' }); + sendToTauri('status', { message: 'Loading core configuration files...' }); + const patcher = new Patcher(APPDATA_DIR); serviceManager = new ServiceManager(APPDATA_DIR, envConfig); @@ -115,7 +116,7 @@ async function start() { attempts++; try { debugLog(`Attempting to download update (Attempt ${attempts})...`); - sendToTauri('status', { message: `Downloading update (Attempt ${attempts}/3)...` }); + sendToTauri('status', { message: `Connecting to download servers (Attempt ${attempts}/3)...` }); await updateClient.downloadUpdate(release, tempZipPath, (downloaded, total) => { const percent = total > 0 ? Math.round((downloaded / total) * 100) : 0; @@ -123,23 +124,26 @@ async function start() { }); debugLog('Download complete. Verifying checksum...'); - sendToTauri('status', { message: 'Verifying payload checksum...' }); + sendToTauri('status', { message: 'Verifying SHA-256 integrity...' }); if (!patcher.verifyChecksum(tempZipPath, release.sha256)) { throw new Error('SHA-256 checksum verification failed.'); } debugLog('Checksum verified. Extracting...'); - sendToTauri('status', { message: 'Extracting update files...' }); + sendToTauri('status', { message: 'Unpacking update archive...' }); patcher.purgeOldCode(); patcher.extractZipPayload(tempZipPath); + sendToTauri('status', { message: 'Verifying file integrity...' }); if (!patcher.verifyExtraction()) { throw new Error('Extraction verification failed: critical startup files missing.'); } debugLog('Extraction complete. Applying patches...'); - sendToTauri('status', { message: 'Applying patches & configuring...' }); + sendToTauri('status', { message: 'Applying configuration patches...' }); patcher.applyPatches(replacementsConfig); + + sendToTauri('status', { message: 'Migrating security credentials...' }); patcher.bridgeCredentials(serviceManager.credentialManager); patcher.saveCurrentVersion(release.version, release.sha256); @@ -150,6 +154,7 @@ async function start() { debugLog('Update installed successfully!'); } catch (err) { debugLog(`Download/Install error on attempt ${attempts}: ${err.message}`); + sendToTauri('status', { message: `Install error: ${err.message}. Retrying...` }); if (fs.existsSync(tempZipPath)) { try { fs.unlinkSync(tempZipPath); } catch (e) { } } @@ -162,12 +167,13 @@ async function start() { }; try { + sendToTauri('status', { message: 'Inspecting local workspace and installed files...' }); const isFirstRun = !patcher.verifyExtraction(); const localVersion = patcher.getCurrentVersion(); const localSha = patcher.getCurrentSha(); debugLog(`Is First Run: ${isFirstRun}, Local Version: ${localVersion}`); - sendToTauri('status', { message: 'Checking for updates...' }); + sendToTauri('status', { message: 'Connecting to update servers...' }); let release = null; try { debugLog("Fetching updates from updateClient..."); @@ -175,15 +181,19 @@ async function start() { debugLog(`Update check finished. Found: ${release ? release.version : 'None'}`); } catch (e) { debugLog(`Update check failed (offline?): ${e.message}`); + sendToTauri('status', { message: `Update server check skipped (${e.message})` }); } if (release) { const hasUpdate = release.version !== localVersion || (release.sha256 && release.sha256 !== localSha); if (isFirstRun) { debugLog("First run. Installing remote build..."); + sendToTauri('status', { message: 'First run detected. Preparing initial installation...' }); await installBuild(release); } else if (hasUpdate) { debugLog("Update available. Prompting user..."); + sendToTauri('status', { message: `New update available (${release.version}). Awaiting confirmation...` }); + const tempApp = express(); let userDecision = null; tempApp.use((req, res, next) => { @@ -216,15 +226,18 @@ async function start() { await installBuild(release); } else { debugLog("User skipped update."); + sendToTauri('status', { message: 'Update skipped. Continuing startup...' }); } } else { debugLog("Already on the latest version."); + sendToTauri('status', { message: 'Up to date.' }); } } else if (isFirstRun) { debugLog("No remote release found, but it's the first run. Looking for local update-payload.zip..."); + sendToTauri('status', { message: 'Looking for local payload archive...' }); const localZip = path.join(__dirname, 'update-payload.zip'); if (fs.existsSync(localZip)) { - sendToTauri('status', { message: 'Extracting initial local payload...' }); + sendToTauri('status', { message: 'Extracting local baseline payload...' }); patcher.purgeOldCode(); patcher.extractZipPayload(localZip); patcher.applyPatches(replacementsConfig); @@ -236,14 +249,15 @@ async function start() { } } + sendToTauri('status', { message: 'Validating critical components...' }); if (!patcher.verifyExtraction()) { throw new Error('No application files found. Check your internet connection and try again.'); } - sendToTauri('status', { message: 'Loading credentials...' }); + sendToTauri('status', { message: 'Loading encrypted credentials...' }); serviceManager.credentialManager.loadCredentials(); - sendToTauri('status', { message: 'Installing the Vyla SDK to latest version...' }); + sendToTauri('status', { message: 'Checking SDK dependencies...' }); try { require('child_process').execSync('npm install @vyla-entertainment/sdk@latest --no-save --no-audit --no-fund', { cwd: APPDATA_DIR, @@ -255,13 +269,14 @@ async function start() { debugLog(`Failed to install Vyla SDK: ${e.message}`); } - sendToTauri('status', { message: 'Starting backend services...' }); + sendToTauri('status', { message: 'Starting background services (auth-proxy, stream-api)...' }); await serviceManager.startServices([ { name: 'auth-proxy', relativeDir: 'auth-proxy' }, { name: 'stream-api', relativeDir: 'stream-api' }, ]); debugLog("Backend services spawn requested."); + sendToTauri('status', { message: 'Binding player web server on port 3001...' }); const playerDir = path.join(APPDATA_DIR, 'player'); playerExpressApp = express(); playerExpressApp.use((req, res, next) => { @@ -282,10 +297,11 @@ async function start() { playerExpressServer = playerExpressApp.listen(3001, '0.0.0.0'); debugLog("Player Express running on 3001"); - sendToTauri('status', { message: 'Waiting for ports 3000, 3001, 7860...' }); + sendToTauri('status', { message: 'Waiting for microservices to bind ports (3000, 3001, 7860)...' }); await serviceManager.waitForPorts([3000, 3001, 7860]); debugLog("Ports 3000, 3001, 7860 are ready."); + sendToTauri('status', { message: 'Binding UI server on port 4890...' }); const frontendDir = path.join(APPDATA_DIR, 'frontend'); expressApp = express(); expressApp.use((req, res, next) => { @@ -305,7 +321,13 @@ async function start() { expressApp.get('*', (req, res) => { res.sendFile(path.join(frontendDir, 'index.html')); }); const FRONTEND_PORT = 4890; + expressApp.use('/build', express.static(path.join(__dirname, 'build'))); + if (playerExpressApp) { + playerExpressApp.use('/build', express.static(path.join(__dirname, 'build'))); + } + expressServer = expressApp.listen(FRONTEND_PORT, '0.0.0.0', () => { + sendToTauri('status', { message: 'All backend services running and healthy.' }); debugLog("Frontend Express running on 4890. Sending READY to Tauri."); sendToTauri('ready', { url: `http://localhost:${FRONTEND_PORT}` }); }); diff --git a/service-manager.js b/service-manager.js index 530b2a6..db7d643 100644 --- a/service-manager.js +++ b/service-manager.js @@ -90,6 +90,20 @@ class ServiceManager { child.stderr.on('data', (data) => { const text = data.toString().trim(); this.logToFile(name, `STDERR: ${text}`); + + if (text.includes('EADDRINUSE')) { + const match = text.match(/:(\d+)/); + const port = match ? match[1] : null; + if (port) { + try { + if (process.platform === 'win32') { + require('child_process').execSync(`powershell -Command "Get-NetTCPConnection -LocalPort ${port} -ErrorAction SilentlyContinue | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force }"`, { stdio: 'ignore' }); + } else { + require('child_process').execSync(`fuser -k -9 ${port}/tcp`, { stdio: 'ignore' }); + } + } catch (_) { } + } + } }); child.on('exit', (code, signal) => { @@ -111,7 +125,7 @@ class ServiceManager { this.startService(name, relativeDir, startScript).catch(err => { this.logToFile(name, `Failed to restart: ${err.message}`); }); - }, 3000); + }, 1500); } }); } diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 012008f..dbb4aca 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -4224,6 +4224,7 @@ dependencies = [ name = "vyla-home" version = "1.1.6" dependencies = [ + "base64 0.22.1", "serde", "serde_json", "tauri", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 1ee8416..a01fbec 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -15,3 +15,4 @@ tauri-plugin-shell = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" url = "2" +base64 = "0.22" diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 3ffc882..8efc939 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -7,9 +7,14 @@ use tauri::{Emitter, Manager, State, WebviewUrl, WebviewWindowBuilder}; fn debug_log(msg: &str) { 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") + 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") + std::path::PathBuf::from(user_profile) + .join(".vyla-home") + .join("logs") } else { std::path::PathBuf::from("logs") }; @@ -17,7 +22,11 @@ fn debug_log(msg: &str) { 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) { + 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); } @@ -40,24 +49,60 @@ fn clean_path(path: &std::path::Path) -> String { } } +#[cfg(windows)] +fn kill_processes_on_ports(ports: &[u16]) { + use std::os::windows::process::CommandExt; + const CREATE_NO_WINDOW: u32 = 0x08000000; + + let output = Command::new("netstat") + .args(["-ano", "-p", "tcp"]) + .creation_flags(CREATE_NO_WINDOW) + .output(); + + if let Ok(out) = output { + let text = String::from_utf8_lossy(&out.stdout); + for line in text.lines() { + for port in ports { + let pattern = format!(":{}", port); + if line.contains(&pattern) && line.contains("LISTENING") { + if let Some(pid_str) = line.split_whitespace().last() { + if let Ok(pid) = pid_str.parse::() { + if pid > 0 && pid != std::process::id() { + let _ = Command::new("taskkill") + .args(["/PID", &pid.to_string(), "/T", "/F"]) + .creation_flags(CREATE_NO_WINDOW) + .output(); + } + } + } + } + } + } + } +} + +#[cfg(not(windows))] +fn kill_processes_on_ports(ports: &[u16]) { + for port in ports { + let _ = Command::new("sh") + .arg("-c") + .arg(format!( + "fuser -k -9 {}/tcp 2>/dev/null || lsof -ti :{} | xargs -r kill -9", + port, port + )) + .output(); + } +} + #[cfg(windows)] fn kill_process_tree(pid: u32) { use std::os::windows::process::CommandExt; const CREATE_NO_WINDOW: u32 = 0x08000000; - let result = Command::new("taskkill") + let _ = Command::new("taskkill") .args(["/PID", &pid.to_string(), "/T", "/F"]) .creation_flags(CREATE_NO_WINDOW) .output(); - match result { - Ok(output) => debug_log(&format!( - "[KILL-TREE] taskkill on PID {} -> status: {}, stdout: {}, stderr: {}", - pid, - output.status, - String::from_utf8_lossy(&output.stdout).trim(), - String::from_utf8_lossy(&output.stderr).trim() - )), - Err(e) => debug_log(&format!("[KILL-TREE] taskkill failed to spawn for PID {}: {}", pid, e)), - } + kill_processes_on_ports(&[3000, 3001, 4890, 4891, 4892, 7860]); } #[cfg(not(windows))] @@ -65,6 +110,7 @@ fn kill_process_tree(pid: u32) { unsafe { libc_kill_pgid(pid); } + kill_processes_on_ports(&[3000, 3001, 4890, 4891, 4892, 7860]); } #[cfg(not(windows))] @@ -164,11 +210,12 @@ fn resolve_script_and_root(app: &tauri::App) -> (String, String) { (clean_path(&candidate), clean_path(&dir)) } -const LINK_HANDLER_SCRIPT: &str = r#" +const LINK_HANDLER_SCRIPT: &str = r##" function doInvoke(cmd, args) { if (window.__TAURI__ && window.__TAURI__.core) { - window.__TAURI__.core.invoke(cmd, args || {}); + return window.__TAURI__.core.invoke(cmd, args || {}); } + return Promise.resolve(null); } function minimizeWindow() { doInvoke('win_minimize'); } @@ -176,6 +223,16 @@ const LINK_HANDLER_SCRIPT: &str = r#" function closeWindow() { doInvoke('win_close'); } function dragWindow() { doInvoke('win_drag'); } + function openExternalUrl(url) { + if (window.__TAURI__ && window.__TAURI__.opener) { + window.__TAURI__.opener.openUrl(url); + } else if (window.__TAURI__ && window.__TAURI__.shell) { + window.__TAURI__.shell.open(url); + } else { + window.open(url, '_blank'); + } + } + window.open = function(url, target, features) { doInvoke('open_new_window', { url: url }); return null; @@ -185,6 +242,13 @@ const LINK_HANDLER_SCRIPT: &str = r#" const a = e.target.closest('a'); if (!a || !a.href) return; + if (a.href.includes('discord.gg') || a.href.includes('discord.com')) { + e.preventDefault(); + e.stopPropagation(); + openExternalUrl(a.href); + return; + } + if (a.target === '_blank' || e.ctrlKey || e.metaKey || e.button === 1) { e.preventDefault(); e.stopPropagation(); @@ -192,16 +256,126 @@ const LINK_HANDLER_SCRIPT: &str = r#" } }, true); - function injectTitlebar() { - if (document.getElementById('tauri-custom-titlebar')) { - return; - } - if (!document.body) { - return; - } + function injectLiquidNavBar() { + if (document.getElementById('vyla-floating-nav')) return; + if (!document.body) return; const style = document.createElement('style'); style.innerHTML = ` + #vyla-nav-filter-svg { + position: fixed !important; + top: 0 !important; + left: 0 !important; + width: 0 !important; + height: 0 !important; + pointer-events: none !important; + opacity: 0 !important; + z-index: -1 !important; + } + + #vyla-floating-nav { + position: fixed !important; + top: 12px !important; + left: 12px !important; + z-index: 2147483647 !important; + height: 42px !important; + width: 42px !important; + border-radius: 9999px !important; + display: flex !important; + align-items: center !important; + background: rgba(255, 255, 255, 0.04) !important; + backdrop-filter: url(#vyla-glass-filter) blur(16px) saturate(1.8) contrast(1.1) !important; + -webkit-backdrop-filter: blur(16px) saturate(1.8) contrast(1.1) !important; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.45) !important; + transition: width 0.3s cubic-bezier(0.16, 1, 0.3, 1), padding 0.3s cubic-bezier(0.16, 1, 0.3, 1) !important; + overflow: hidden !important; + padding: 0 !important; + user-select: none !important; + pointer-events: auto !important; + -webkit-app-region: no-drag !important; + } + + #vyla-floating-nav:hover { + width: 82px !important; + padding-right: 6px !important; + } + + .vyla-nav-logo-wrap { + width: 42px !important; + min-width: 42px !important; + height: 42px !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + cursor: pointer !important; + border-radius: 9999px !important; + position: relative !important; + z-index: 2147483647 !important; + pointer-events: auto !important; + padding: 0 !important; + } + + .vyla-nav-logo-wrap img { + width: 26px !important; + height: 26px !important; + min-width: 26px !important; + min-height: 26px !important; + object-fit: contain !important; + pointer-events: none !important; + display: block !important; + } + + .vyla-nav-links { + display: flex !important; + align-items: center !important; + gap: 4px !important; + opacity: 0 !important; + transform: translateX(-6px) !important; + transition: opacity 0.2s ease, transform 0.25s cubic-bezier(0.16, 1, 0.3, 1) !important; + pointer-events: none !important; + position: relative !important; + z-index: 2 !important; + margin-left: 2px !important; + } + + #vyla-floating-nav:hover .vyla-nav-links { + opacity: 1 !important; + transform: translateX(0) !important; + pointer-events: auto !important; + } + + .vyla-nav-btn { + width: 32px !important; + min-width: 32px !important; + height: 32px !important; + border-radius: 9999px !important; + border: none !important; + outline: none !important; + background: transparent !important; + box-shadow: none !important; + color: #ffffff !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + cursor: pointer !important; + transition: transform 0.15s ease, opacity 0.2s ease !important; + opacity: 0.85 !important; + padding: 0 !important; + } + + .vyla-nav-btn:hover { + transform: scale(1.15) !important; + opacity: 1 !important; + } + + .vyla-nav-btn svg { + width: 20px !important; + height: 20px !important; + fill: #ffffff !important; + pointer-events: none !important; + display: block !important; + } + #tauri-custom-titlebar { position: fixed !important; top: 0 !important; @@ -210,7 +384,7 @@ const LINK_HANDLER_SCRIPT: &str = r#" height: 32px !important; max-height: 32px !important; display: flex !important; - justify-content: space-between !important; + justify-content: flex-end !important; align-items: center !important; z-index: 2147483647 !important; background: transparent !important; @@ -219,13 +393,15 @@ const LINK_HANDLER_SCRIPT: &str = r#" margin: 0 !important; padding: 0 !important; } + .tauri-drag-region { - flex: 1 !important; + position: absolute !important; + inset: 0 !important; height: 32px !important; - margin-left: 100px !important; cursor: default !important; pointer-events: auto !important; } + .tauri-window-controls { display: flex !important; height: 32px !important; @@ -234,13 +410,12 @@ const LINK_HANDLER_SCRIPT: &str = r#" pointer-events: auto !important; z-index: 2147483648 !important; } + button.tauri-btn { width: 46px !important; min-width: 46px !important; max-width: 46px !important; height: 32px !important; - min-height: 32px !important; - max-height: 32px !important; background: transparent !important; border: none !important; color: #ffffff !important; @@ -249,22 +424,114 @@ const LINK_HANDLER_SCRIPT: &str = r#" 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; - pointer-events: auto !important; - user-select: none !important; } + button.tauri-btn:hover { background: rgba(255, 255, 255, 0.2) !important; } button#tauri-btn-close:hover { background: #e81123 !important; color: #ffffff !important; } - button.tauri-btn svg { width: 10px !important; height: 10px !important; fill: currentColor !important; pointer-events: none !important; display: block !important; } + button.tauri-btn svg { width: 10px !important; height: 10px !important; fill: currentColor !important; pointer-events: none !important; } `; document.head.appendChild(style); + const filterSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + filterSvg.id = 'vyla-nav-filter-svg'; + filterSvg.innerHTML = ` + + + + + + + + + + + + + + + `; + document.body.appendChild(filterSvg); + + const nav = document.createElement('div'); + nav.id = 'vyla-floating-nav'; + nav.innerHTML = ` +
+ Logo +
+ + `; + document.body.appendChild(nav); + + const logoBtn = nav.querySelector('.vyla-nav-logo-wrap'); + if (logoBtn) { + logoBtn.onclick = (e) => { + e.preventDefault(); + e.stopPropagation(); + window.location.href = 'http://localhost:4890'; + }; + } + + const btnDiscord = nav.querySelector('#vyla-btn-discord'); + if (btnDiscord) { + btnDiscord.onclick = (e) => { + e.preventDefault(); + e.stopPropagation(); + openExternalUrl('https://discord.gg/5eqRhKeywG'); + }; + } + + const feImage = document.getElementById('vyla-nav-fe-img'); + const redChannel = document.getElementById('vyla-nav-red'); + const greenChannel = document.getElementById('vyla-nav-green'); + const blueChannel = document.getElementById('vyla-nav-blue'); + + function generateNavMap() { + const rect = nav.getBoundingClientRect(); + const w = Math.round(rect.width) || 42; + const h = Math.round(rect.height) || 42; + const edgeSize = Math.min(w, h) * 0.08; + const svgContent = ` + + + + + + + + + + + + + + + + + `; + return "data:image/svg+xml," + encodeURIComponent(svgContent); + } + + function updateNavGlass() { + if (!feImage) return; + feImage.setAttribute("href", generateNavMap()); + const distortionScale = -180; + redChannel.setAttribute("scale", distortionScale - 10); + greenChannel.setAttribute("scale", distortionScale); + blueChannel.setAttribute("scale", distortionScale + 10); + [redChannel, greenChannel, blueChannel].forEach((ch) => { + ch.setAttribute("xChannelSelector", "R"); + ch.setAttribute("yChannelSelector", "G"); + }); + } + + updateNavGlass(); + const obs = new ResizeObserver(() => setTimeout(updateNavGlass, 0)); + obs.observe(nav); + const tb = document.createElement('div'); tb.id = 'tauri-custom-titlebar'; tb.innerHTML = ` @@ -275,7 +542,6 @@ const LINK_HANDLER_SCRIPT: &str = r#" `; - document.body.appendChild(tb); const btnMin = tb.querySelector('#tauri-btn-min'); @@ -295,18 +561,14 @@ const LINK_HANDLER_SCRIPT: &str = r#" }; } } - - injectTitlebar(); - setInterval(injectTitlebar, 1000); if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', injectTitlebar); + document.addEventListener('DOMContentLoaded', injectLiquidNavBar); } else { - injectTitlebar(); + injectLiquidNavBar(); } - - window.addEventListener('load', injectTitlebar); -"#; + setInterval(injectLiquidNavBar, 1000); +"##; #[tauri::command] fn win_minimize(window: tauri::Window) -> Result<(), String> { @@ -350,7 +612,10 @@ fn open_new_window(app: tauri::AppHandle, url: String) -> Result<(), String> { if let Some(window) = app.get_webview_window("main") { window - .eval(&format!("window.location.href = '{}';", final_url.replace('\'', "\\'"))) + .eval(&format!( + "window.location.href = '{}';", + final_url.replace('\'', "\\'") + )) .map_err(|e| e.to_string())?; Ok(()) } else { @@ -358,6 +623,39 @@ fn open_new_window(app: tauri::AppHandle, url: String) -> Result<(), String> { } } +#[tauri::command] +fn get_logo_base64(app: tauri::AppHandle) -> Result { + use std::io::Read; + let mut candidate_paths = Vec::new(); + + if let Ok(res_dir) = app.path().resource_dir() { + candidate_paths.push(res_dir.join("build").join("icon_white.png")); + candidate_paths.push(res_dir.join("_up_").join("build").join("icon_white.png")); + } + + if let Ok(mut cur) = std::env::current_dir() { + candidate_paths.push(cur.join("build").join("icon_white.png")); + if cur.ends_with("src-tauri") { + cur.pop(); + candidate_paths.push(cur.join("build").join("icon_white.png")); + } + } + + for path in candidate_paths { + if path.exists() { + if let Ok(mut file) = std::fs::File::open(&path) { + let mut buffer = Vec::new(); + if file.read_to_end(&mut buffer).is_ok() { + use base64::Engine; + return Ok(base64::engine::general_purpose::STANDARD.encode(buffer)); + } + } + } + } + + Err("icon_white.png not found".to_string()) +} + fn main() { debug_log("=== Vyla Home Executable Started ==="); @@ -370,24 +668,26 @@ fn main() { win_maximize, win_close, win_drag, - open_new_window + open_new_window, + get_logo_base64 ]) .manage(NodeServices(Mutex::new(None))) .setup(|app| { debug_log("Tauri setup started"); + kill_processes_on_ports(&[3000, 3001, 4890, 4891, 4892, 7860]); let app_data = app.path().app_data_dir().unwrap_or_default(); let app_data_str = clean_path(&app_data); -let window = WebviewWindowBuilder::new(app, "main", WebviewUrl::App("index.html".into())) - .title("Vyla Home") - .inner_size(1280.0, 800.0) - .decorations(false) - .transparent(true) - .resizable(true) - .devtools(true) - .initialization_script("window.__VYLA_WINDOW_LABEL = 'main';") - .initialization_script(LINK_HANDLER_SCRIPT) - .build()?; + let window = WebviewWindowBuilder::new(app, "main", WebviewUrl::App("index.html".into())) + .title("Vyla Home") + .inner_size(1280.0, 800.0) + .decorations(false) + .transparent(true) + .resizable(true) + .devtools(true) + .initialization_script("window.__VYLA_WINDOW_LABEL = 'main';") + .initialization_script(LINK_HANDLER_SCRIPT) + .build()?; let (main_js, root_dir) = resolve_script_and_root(app); let node_bin = match find_node_binary(app) { @@ -420,7 +720,10 @@ let window = WebviewWindowBuilder::new(app, "main", WebviewUrl::App("index.html" match cmd.spawn() { Ok(mut child) => { - debug_log(&format!("Node child process spawned successfully, PID: {}", child.id())); + debug_log(&format!( + "Node child process spawned successfully, PID: {}", + child.id() + )); if let Some(stderr) = child.stderr.take() { std::thread::spawn(move || { use std::io::{BufRead, BufReader}; @@ -440,76 +743,143 @@ let window = WebviewWindowBuilder::new(app, "main", WebviewUrl::App("index.html" debug_log(&format!("[NODE STDOUT] {}", line)); if let Ok(msg) = serde_json::from_str::(&line) { debug_log(&format!("[MAIN-DEBUG] Received event: {}", msg.event)); - debug_log(&format!("[MAIN-DEBUG] Event data: {}", serde_json::to_string(&msg.data).unwrap_or_default())); + debug_log(&format!( + "[MAIN-DEBUG] Event data: {}", + serde_json::to_string(&msg.data).unwrap_or_default() + )); if msg.event == "tauri-cmd" { - let window_label = msg.data.get("window").and_then(|val| val.as_str()).unwrap_or("main"); - debug_log(&format!("[MAIN-DEBUG] Finding window by label: {}", window_label)); + let window_label = msg + .data + .get("window") + .and_then(|val| val.as_str()) + .unwrap_or("main"); + debug_log(&format!( + "[MAIN-DEBUG] Finding window by label: {}", + window_label + )); - 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()) { - debug_log(&format!("[TAURI-CMD] Executing action: {}", cmd)); + 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()) + { + debug_log(&format!( + "[TAURI-CMD] Executing action: {}", + cmd + )); match cmd { "win_minimize" => { let res = cmd_window.minimize(); - debug_log(&format!("[TAURI-CMD] Minimize result: {:?}", res)); - }, + debug_log(&format!( + "[TAURI-CMD] Minimize result: {:?}", + res + )); + } "win_maximize" => { - let is_max = cmd_window.is_maximized().unwrap_or(false); - debug_log(&format!("[TAURI-CMD] Currently maximized: {}", is_max)); - let res = if is_max { cmd_window.unmaximize() } else { cmd_window.maximize() }; - debug_log(&format!("[TAURI-CMD] Maximize/Unmaximize result: {:?}", res)); - }, + let is_max = cmd_window + .is_maximized() + .unwrap_or(false); + debug_log(&format!( + "[TAURI-CMD] Currently maximized: {}", + is_max + )); + let res = if is_max { + cmd_window.unmaximize() + } else { + cmd_window.maximize() + }; + debug_log(&format!( + "[TAURI-CMD] Maximize/Unmaximize result: {:?}", + res + )); + } "win_close" => { let res = cmd_window.close(); - debug_log(&format!("[TAURI-CMD] Close result: {:?}", res)); - }, + debug_log(&format!( + "[TAURI-CMD] Close result: {:?}", + res + )); + } "win_drag" => { let res = cmd_window.start_dragging(); - debug_log(&format!("[TAURI-CMD] Drag result: {:?}", res)); - }, -"open_new_window" => { - debug_log("[TAURI-CMD-DEBUG] open_new_window command received"); - debug_log(&format!("[TAURI-CMD-DEBUG] Full message data: {}", serde_json::to_string(&msg.data).unwrap_or_default())); - if let Some(url) = msg.data.get("url").and_then(|u| u.as_str()) { - debug_log(&format!("[TAURI-CMD-DEBUG] URL extracted: {}", url)); - debug_log(&format!("[TAURI-CMD-DEBUG] URL length: {}", url.len())); - debug_log("[TAURI-CMD-DEBUG] Attempting to dispatch to main thread"); - let app_handle = w.app_handle().clone(); - let closure_handle = app_handle.clone(); - let url_owned = url.to_string(); - let dispatch_result = app_handle.run_on_main_thread(move || { - debug_log("[TAURI-CMD-DEBUG] Now on main thread, calling open_new_window"); - match open_new_window(closure_handle, url_owned) { - Ok(_) => { - debug_log("[TAURI-CMD-DEBUG] open_new_window succeeded"); - }, - Err(e) => { - debug_log(&format!("[TAURI-CMD-DEBUG] open_new_window error: {}", e)); - }, - } - }); - match dispatch_result { - Ok(_) => debug_log("[TAURI-CMD-DEBUG] Main thread dispatch succeeded"), - Err(e) => debug_log(&format!("[TAURI-CMD-DEBUG] Main thread dispatch failed: {}", e)), - } - } else { - debug_log("[TAURI-CMD-DEBUG] ERROR: No URL found in message data"); - debug_log(&format!("[TAURI-CMD-DEBUG] Available keys: {:?}", msg.data.as_object().map(|o| o.keys().collect::>()))); - } -}, + debug_log(&format!( + "[TAURI-CMD] Drag result: {:?}", + res + )); + } + "open_new_window" => { + debug_log("[TAURI-CMD-DEBUG] open_new_window command received"); + debug_log(&format!( + "[TAURI-CMD-DEBUG] Full message data: {}", + serde_json::to_string(&msg.data) + .unwrap_or_default() + )); + if let Some(url) = msg + .data + .get("url") + .and_then(|u| u.as_str()) + { + debug_log(&format!( + "[TAURI-CMD-DEBUG] URL extracted: {}", + url + )); + debug_log(&format!( + "[TAURI-CMD-DEBUG] URL length: {}", + url.len() + )); + debug_log("[TAURI-CMD-DEBUG] Attempting to dispatch to main thread"); + let app_handle = w.app_handle().clone(); + let closure_handle = app_handle.clone(); + let url_owned = url.to_string(); + let dispatch_result = app_handle + .run_on_main_thread(move || { + debug_log("[TAURI-CMD-DEBUG] Now on main thread, calling open_new_window"); + match open_new_window( + closure_handle, + url_owned, + ) { + Ok(_) => { + debug_log("[TAURI-CMD-DEBUG] open_new_window succeeded"); + } + Err(e) => { + debug_log(&format!( + "[TAURI-CMD-DEBUG] open_new_window error: {}", + e + )); + } + } + }); + match dispatch_result { + Ok(_) => debug_log( + "[TAURI-CMD-DEBUG] Main thread dispatch succeeded", + ), + Err(e) => debug_log(&format!( + "[TAURI-CMD-DEBUG] Main thread dispatch failed: {}", + e + )), + } + } else { + debug_log("[TAURI-CMD-DEBUG] ERROR: No URL found in message data"); + debug_log(&format!( + "[TAURI-CMD-DEBUG] Available keys: {:?}", + msg.data + .as_object() + .map(|o| o.keys().collect::>()) + )); + } + } _ => {} } } } else { - debug_log(&format!("[TAURI-CMD] Window '{}' NOT FOUND!", window_label)); + debug_log(&format!( + "[TAURI-CMD] Window '{}' NOT FOUND!", + window_label + )); } } 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)); - } - } } } } @@ -523,7 +893,12 @@ let window = WebviewWindowBuilder::new(app, "main", WebviewUrl::App("index.html" } 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) + }), + ); } } @@ -536,10 +911,14 @@ let window = WebviewWindowBuilder::new(app, "main", WebviewUrl::App("index.html" if let Ok(mut lock) = state.0.lock() { if let Some(child) = lock.take() { let pid = child.id(); - debug_log(&format!("[SHUTDOWN] Main window destroyed, killing process tree for PID {}", pid)); + debug_log(&format!( + "[SHUTDOWN] Main window destroyed, killing process tree for PID {}", + pid + )); kill_process_tree(pid); } }; + kill_processes_on_ports(&[3000, 3001, 4890, 4891, 4892, 7860]); } } }) diff --git a/ui/index.html b/ui/index.html index 5952d3a..94cbe46 100644 --- a/ui/index.html +++ b/ui/index.html @@ -7,643 +7,284 @@ -
-
- - - - - - - - - - - - - +
-

Starting Vyla Home

-

Initializing services...

- -
-
+
+
+
Starting runtime engine...
+ -
-
-
-
- - - -

Update Available

-

A new version is available.

-
- - + + +
@@ -665,116 +306,347 @@
- + + - \ No newline at end of file