using System.Diagnostics; using System.IO; using UnityEngine; using System; using System.Collections; using Debug = UnityEngine.Debug; public class WebViewLauncher : MonoBehaviour { public static WebViewLauncher Instance { get; private set; } [Header("Web Settings")] public string startUrl = "https://www.baidu.com"; public Vector2 windowSize = new Vector2(960, 600); public Vector2 windowPosition = new Vector2(100, 100); [Header("Editor Only Path")] public string editorExeRoot = @"E:\Unity\ban_total\fdBrowser"; [Header("H5 Folder Paths")] public string h5EditorPath = @"E:\Unity\ban_total\ban_test\Bansonic_Data\StreamingAssets\h5LG"; public string officialDocsPath = "_officialDocs"; public string officialGamesPath = "_officialGames"; public string thirdPartyGamesPath = "_thirdPartyGames"; private Process webProcess; // Event invoked when web process main window handle becomes available public event Action WebViewReady; void Awake() { if (Instance != null) { Destroy(gameObject); return; } Instance = this; DontDestroyOnLoad(gameObject); } void Start() { // 扫描 H5 文件夹 string h5Folder = GetH5FolderPath(); if (!Directory.Exists(h5Folder)) { Directory.CreateDirectory(h5Folder); Debug.Log("Created H5 folder: " + h5Folder); } var h5SubFolders = ScanH5Folders(h5Folder); if (h5SubFolders.Count > 0) { Debug.Log("Found H5 folders: " + string.Join(", ", h5SubFolders)); string selectedFolder = h5SubFolders[0]; // 选择第一个 startUrl = "file://" + Path.Combine(h5Folder, selectedFolder, "index.html"); Debug.Log("Selected H5 folder: " + selectedFolder + ", URL: " + startUrl); } else { Debug.LogWarning("No H5 subfolders with index.html found in: " + h5Folder); } LaunchWebView(); } void OnDestroy() { KillWebView(); } public void KillWebView() { if (webProcess != null && !webProcess.HasExited) { webProcess.Kill(); webProcess = null; } } void LaunchWebView() { string exePath = GetExePath(); Debug.Log("WebView exe path: " + exePath); if (!File.Exists(exePath)) { Debug.LogError("fdBrowser.exe not found: " + exePath); return; } // 初始参数可以给 0,后续由 PanelFollower 接管 string args = $"0 0 800 600 \"{startUrl}\""; Debug.Log($"Launching: {exePath} {args}"); ProcessStartInfo psi = new ProcessStartInfo { FileName = exePath, Arguments = args, UseShellExecute = true, // 改为 true 以确保 exe 能启动 WorkingDirectory = Path.GetDirectoryName(exePath) }; try { webProcess = Process.Start(psi); Debug.Log("WebView2Host started successfully. Process ID: " + webProcess.Id); // Start coroutine to wait until main window handle is ready StartCoroutine(WaitForMainWindowHandle()); } catch (System.Exception e) { Debug.LogError("Failed to start WebView2Host: " + e.Message + "\nPath: " + exePath + "\nArgs: " + args); } } IEnumerator WaitForMainWindowHandle() { if (webProcess == null) yield break; float timeout = 5f; // seconds float elapsed = 0f; while (elapsed < timeout) { try { webProcess.Refresh(); if (webProcess.MainWindowHandle != IntPtr.Zero) { Debug.Log($"WebViewLauncher: MainWindowHandle ready: 0x{webProcess.MainWindowHandle.ToInt64():X}"); WebViewReady?.Invoke(); yield break; } } catch (Exception e) { Debug.LogWarning("WebViewLauncher.WaitForMainWindowHandle exception: " + e.Message); } yield return null; elapsed += Time.deltaTime; } Debug.LogWarning("WebViewLauncher: timeout waiting for main window handle"); // still invoke event so follower can attempt positioning later WebViewReady?.Invoke(); } /// /// 从外部请求打开一个 url。如果 webview 没有运行则启动;如果已运行则尝试通过 WM_COPYDATA 发送 url 给已有窗口,失败则重启。 /// 传入的 url 建议使用本地文件路径或以 file:// 开头的路径。 /// /// public void OpenUrl(string url) { if (string.IsNullOrEmpty(url)) { Debug.LogWarning("OpenUrl called with empty url"); return; } // Normalize to absolute file:// or keep as web URL string final; try { if (Uri.TryCreate(url, UriKind.Absolute, out var parsed) && !string.IsNullOrEmpty(parsed.Scheme)) { // If it's already a file/http/https URI, use its AbsoluteUri final = parsed.AbsoluteUri; } else { // Treat as local file path string abs = Path.GetFullPath(url); final = new Uri(abs).AbsoluteUri; // yields file:///C:/... } } catch (Exception e) { Debug.LogWarning("OpenUrl: failed to normalize url '" + url + "' : " + e.Message); final = url; } Debug.Log($"OpenUrl requested. original='{url}', normalized='{final}'"); startUrl = final; // if process not running, launch; otherwise try to send url to existing window if (webProcess == null || webProcess.HasExited) { Debug.Log("WebView process not running. Launching with URL: " + final); LaunchWebView(); return; } try { Debug.Log($"WebView process running. PID={webProcess.Id}, MainWindowHandle=0x{webProcess.MainWindowHandle.ToInt64():X}"); } catch { } // 尝试通过 WM_COPYDATA 将 URL 发送给已有进程窗口(需要 fdBrowser 实现接收处理) bool sent = WebViewWin32.TrySendUrlToWindow(webProcess, final); Debug.Log($"TrySendUrlToWindow returned: {sent}"); if (sent) { Debug.Log("Sent URL to existing WebView process: " + final); // Notify followers that webview content may have changed and they should reapply positioning WebViewReady?.Invoke(); return; } // 发送失败,退回到重启进程打开 URL 的方式 Debug.Log("Failed to send URL to existing process, restarting to open: " + final); KillWebView(); LaunchWebView(); // after relaunch, WaitForMainWindowHandle will invoke WebViewReady when handle ready } string GetExePath() { #if UNITY_EDITOR return Path.Combine(editorExeRoot, "fdBrowser.exe"); #else // 优先使用 StreamingAssets 路径 string streamingPath = Path.Combine(Application.streamingAssetsPath, "fdBrowser", "fdBrowser.exe"); if (File.Exists(streamingPath)) { Debug.Log("Using StreamingAssets path: " + streamingPath); return streamingPath; } // 回退到游戏根目录 string gameRoot = Path.GetDirectoryName(Application.dataPath); string fallbackPath = Path.Combine(gameRoot, "fdBrowser", "fdBrowser.exe"); Debug.Log("Using fallback path: " + fallbackPath); return fallbackPath; #endif } string GetH5FolderPath() { #if UNITY_EDITOR return h5EditorPath; #else return Path.Combine(Application.streamingAssetsPath, "h5LG"); #endif } System.Collections.Generic.List ScanH5Folders(string folderPath) { var result = new System.Collections.Generic.List(); if (!Directory.Exists(folderPath)) { Debug.LogWarning("H5 folder does not exist: " + folderPath); return result; } Debug.Log("Scanning H5 folders in: " + folderPath); string[] targetFolders = { officialDocsPath, officialGamesPath, thirdPartyGamesPath }; foreach (string folderName in targetFolders) { string subFolderPath = Path.Combine(folderPath, folderName); if (!Directory.Exists(subFolderPath)) { Directory.CreateDirectory(subFolderPath); Debug.Log("Created subfolder: " + subFolderPath); } string indexPath = Path.Combine(subFolderPath, "index.html"); if (!File.Exists(indexPath)) { File.WriteAllText(indexPath, "

" + folderName + "

"); Debug.Log("Created index.html in: " + subFolderPath); } if (File.Exists(indexPath)) { result.Add(folderName); Debug.Log("Found H5 folder with index.html: " + folderName); } } Debug.Log("Total H5 folders found: " + result.Count); return result; } /// /// 给 PanelFollower 调用 /// public bool TryUpdateWebViewRect(int x, int y, int w, int h) { if (webProcess == null || webProcess.HasExited) return false; return WebViewWin32.TrySetWindowRect(webProcess, x, y, w, h); } }