using System.Diagnostics; using System.IO; using UnityEngine; using System; using System.Collections; using UnityEngine.UI; using UnityEngine.EventSystems; using System.Text.RegularExpressions; 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; [Header("Control Buttons (optional)")] public Button refreshButton; // Documentation text normalized. public Button closeButton; // Documentation text normalized. public Button restartButton; // Documentation text normalized. public Button backButton; // Documentation text normalized. public Button forwardButton; // Documentation text normalized. public Button startRestartButton; // Documentation text normalized. public Button forceKillButton; // Documentation text normalized. public Button hideWindowButton; // Documentation text normalized. public Button showWindowButton; // Documentation text normalized. public Button toggleTextButton; // Documentation text normalized. [Header("Text to Toggle")] public Text controlText; // Documentation text normalized. [Header("Autostart")] public UnityEngine.UI.Toggle autostartToggle; // UI toggle to control auto-start private bool autostart = true; private const string PREF_AUTOSTART = "WebView_AutoStart"; [Header("Homepage / URL UI")] public Button homepageButton; // Documentation text normalized. public InputField urlInputField; // Documentation text normalized. [Tooltip("Homepage URL (can be file:// or http(s) or local path)")] public string homepageUrl = ""; [Header("Go button")] public Button goButton; // Documentation text normalized. void Awake() { if (Instance != null) { Destroy(gameObject); return; } Instance = this; DontDestroyOnLoad(gameObject); // wire up optional UI buttons if (refreshButton != null) refreshButton.onClick.AddListener(RefreshWebView); if (closeButton != null) closeButton.onClick.AddListener(CloseWebView); if (restartButton != null) restartButton.onClick.AddListener(RestartWebView); if (backButton != null) backButton.onClick.AddListener(GoBackWebView); if (forwardButton != null) forwardButton.onClick.AddListener(GoForwardWebView); if (startRestartButton != null) startRestartButton.onClick.AddListener(OnStartRestartButtonClicked); if (forceKillButton != null) forceKillButton.onClick.AddListener(OnForceKillClicked); if (hideWindowButton != null) hideWindowButton.onClick.AddListener(OnHideWindowClicked); if (showWindowButton != null) showWindowButton.onClick.AddListener(OnShowWindowClicked); if (toggleTextButton != null) toggleTextButton.onClick.AddListener(ToggleTextVisibility); if (homepageButton != null) homepageButton.onClick.AddListener(OnHomepageClicked); if (urlInputField != null) urlInputField.onEndEdit.AddListener(OnUrlInputEndEdit); if (goButton != null) goButton.onClick.AddListener(OnGoButtonClicked); // Load autostart preference and bind toggle autostart = PlayerPrefs.GetInt(PREF_AUTOSTART, 1) == 1; if (autostartToggle != null) { autostartToggle.isOn = autostart; autostartToggle.onValueChanged.AddListener(OnAutostartToggleChanged); } // set default homepage if empty if (string.IsNullOrEmpty(homepageUrl)) { #if UNITY_EDITOR string candidate = Path.Combine(h5EditorPath, "_officialDocs", "openings", "index.html"); if (File.Exists(candidate)) homepageUrl = new Uri(candidate).AbsoluteUri; else homepageUrl = startUrl; #else string candidate = Path.Combine(Application.streamingAssetsPath, "h5LG", "_officialDocs", "openings", "index.html"); if (File.Exists(candidate)) homepageUrl = new Uri(candidate).AbsoluteUri; else homepageUrl = startUrl; #endif } } void Start() { // Ensure H5 root exists string h5Folder = GetH5FolderPath(); if (!Directory.Exists(h5Folder)) { Directory.CreateDirectory(h5Folder); Debug.Log("Created H5 folder: " + h5Folder); } // Ensure controlText is visible by default if (controlText != null) { controlText.gameObject.SetActive(true); Debug.Log("Control text set to visible by default"); } // Prefer explicit openings index as startup page when available string preferred = Path.Combine(h5Folder, "_officialDocs", "openings", "index.html"); if (File.Exists(preferred)) { startUrl = new Uri(preferred).AbsoluteUri; Debug.Log("Using preferred startup page: " + startUrl); // show initial url in input field if (urlInputField != null) urlInputField.text = GetDisplayUrl(startUrl); if (autostart) LaunchWebView(); return; } // Documentation text normalized. var h5SubFolders = ScanH5Folders(h5Folder); if (h5SubFolders.Count > 0) { Debug.Log("Found H5 folders: " + string.Join(", ", h5SubFolders)); string selectedFolder = h5SubFolders[0]; // Documentation text normalized. string candidatePath = Path.Combine(h5Folder, selectedFolder, "index.html"); startUrl = new Uri(candidatePath).AbsoluteUri; // ensure proper file:/// URI Debug.Log("Selected H5 folder: " + selectedFolder + ", URL: " + startUrl); } else { Debug.LogWarning("No H5 subfolders with index.html found in: " + h5Folder); } // show initial url in input field if available (use shortened display for local files that are indexed) if (urlInputField != null) { urlInputField.text = GetDisplayUrl(startUrl); } if (autostart) LaunchWebView(); } void OnDestroy() { KillWebView(); // remove listeners if (refreshButton != null) refreshButton.onClick.RemoveListener(RefreshWebView); if (closeButton != null) closeButton.onClick.RemoveListener(CloseWebView); if (restartButton != null) restartButton.onClick.RemoveListener(RestartWebView); if (backButton != null) backButton.onClick.RemoveListener(GoBackWebView); if (forwardButton != null) forwardButton.onClick.RemoveListener(GoForwardWebView); if (startRestartButton != null) startRestartButton.onClick.RemoveListener(OnStartRestartButtonClicked); if (forceKillButton != null) forceKillButton.onClick.RemoveListener(OnForceKillClicked); if (hideWindowButton != null) hideWindowButton.onClick.RemoveListener(OnHideWindowClicked); if (showWindowButton != null) showWindowButton.onClick.RemoveListener(OnShowWindowClicked); if (toggleTextButton != null) toggleTextButton.onClick.RemoveListener(ToggleTextVisibility); if (autostartToggle != null) autostartToggle.onValueChanged.RemoveListener(OnAutostartToggleChanged); if (homepageButton != null) homepageButton.onClick.RemoveListener(OnHomepageClicked); if (urlInputField != null) urlInputField.onEndEdit.RemoveListener(OnUrlInputEndEdit); if (goButton != null) goButton.onClick.RemoveListener(OnGoButtonClicked); } void Update() { // if input field focused and Enter pressed -> navigate if (urlInputField != null && urlInputField.isFocused && (Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter))) { string text = urlInputField.text; if (!string.IsNullOrEmpty(text)) { OpenUrl(text); // remove focus EventSystem.current?.SetSelectedGameObject(null); urlInputField.DeactivateInputField(); } } // clicking elsewhere should unfocus input field (end edit) if (Input.GetMouseButtonDown(0) && urlInputField != null && urlInputField.isFocused) { var sel = EventSystem.current != null ? EventSystem.current.currentSelectedGameObject : null; if (sel != urlInputField.gameObject) { urlInputField.DeactivateInputField(); EventSystem.current?.SetSelectedGameObject(null); } } } void OnGoButtonClicked() { if (urlInputField == null) return; string text = urlInputField.text; if (!string.IsNullOrEmpty(text)) { OpenUrl(text); EventSystem.current?.SetSelectedGameObject(null); urlInputField.DeactivateInputField(); } } public void KillWebView() { if (webProcess != null && !webProcess.HasExited) { try { webProcess.Kill(); } catch (Exception e) { Debug.LogWarning("KillWebView failed: " + e.Message); } 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; } // Documentation text normalized. string args = $"0 0 800 600 \"{startUrl}\""; Debug.Log($"Launching: {exePath} {args}"); ProcessStartInfo psi = new ProcessStartInfo { FileName = exePath, Arguments = args, UseShellExecute = true, // Documentation text normalized. 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(); } /// /// Documentation text normalized. /// Documentation text normalized. /// /// 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 (IsLikelyWebUrl(url)) { if (!Uri.TryCreate(url, UriKind.Absolute, out var parsed) || string.IsNullOrEmpty(parsed.Scheme)) { final = "http://" + url; } else { final = parsed.AbsoluteUri; } } else if (url.StartsWith("localhost::")) { // Map localhost::display back to actual file URI string relativePath = url.Substring("localhost::".Length).Replace('/', Path.DirectorySeparatorChar); string h5Folder = GetH5FolderPath(); string fullPath = Path.Combine(h5Folder, relativePath); final = new Uri(fullPath).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; // update input field display if (urlInputField != null) urlInputField.text = GetDisplayUrl(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 { } // Documentation text normalized. 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; } // Documentation text normalized. Debug.Log("Failed to send URL to existing process, restarting to open: " + final); KillWebView(); LaunchWebView(); // after relaunch, WaitForMainWindowHandle will invoke WebViewReady when handle ready } // Try to send a simple command string to the webview process via WM_COPYDATA. Returns true when SendMessage returned non-zero. bool TrySendCommand(string cmd) { if (webProcess == null || webProcess.HasExited) { Debug.LogWarning("TrySendCommand: webProcess not running"); return false; } bool sent = WebViewWin32.TrySendUrlToWindow(webProcess, cmd); Debug.Log($"TrySendCommand('{cmd}') returned: {sent}"); return sent; } // UI control methods public void RefreshWebView() { // Try sending a refresh command; fallback to restarting to refresh if (TrySendCommand("CMD:REFRESH")) { Debug.Log("Sent REFRESH command to webview"); return; } Debug.Log("REFRESH command not supported, restarting webview to refresh"); KillWebView(); LaunchWebView(); } public void CloseWebView() { Debug.Log("Closing webview process"); KillWebView(); } public void RestartWebView() { Debug.Log("Restarting webview process"); KillWebView(); LaunchWebView(); } public void GoBackWebView() { if (TrySendCommand("CMD:BACK")) { Debug.Log("Sent BACK command to webview"); return; } Debug.LogWarning("BACK command not supported by webview"); } public void GoForwardWebView() { if (TrySendCommand("CMD:FORWARD")) { Debug.Log("Sent FORWARD command to webview"); return; } Debug.LogWarning("FORWARD command not supported by webview"); } void OnHomepageClicked() { // Always navigate to StreamingAssets/h5LG/_officialDocs/openings/index.html string candidate = Path.Combine(Application.streamingAssetsPath, "h5LG", "_officialDocs", "openings", "index.html"); if (!File.Exists(candidate)) { Debug.LogWarning("Homepage index not found at: " + candidate + ". Falling back to homepageUrl or startUrl."); if (!string.IsNullOrEmpty(homepageUrl)) { LaunchWebViewWithUrl(homepageUrl); } else { LaunchWebViewWithUrl(startUrl); } return; } string fileUri = new Uri(candidate).AbsoluteUri; LaunchWebViewWithUrl(fileUri); // OpenUrl will update the input field using GetDisplayUrl(final). Ensure display shows the shortened localhost form. if (urlInputField != null) urlInputField.text = GetDisplayUrl(fileUri); } void OnUrlInputEndEdit(string text) { if (string.IsNullOrEmpty(text)) return; OpenUrl(text); } void OnStartRestartButtonClicked() { if (webProcess == null || webProcess.HasExited) { Debug.Log("WebView process not running. Starting webview."); StartWebView(); } else { Debug.Log("WebView process running. Restarting webview."); RestartWebView(); } } void OnForceKillClicked() { Debug.Log("Force killing webview process"); KillWebView(); } void OnHideWindowClicked() { if (webProcess == null || webProcess.HasExited) { Debug.LogWarning("HideWindow: webview process not running"); return; } bool ok = WebViewWin32.TryShowWindow(webProcess, false); Debug.Log("HideWindow clicked, result: " + ok); } void OnShowWindowClicked() { if (webProcess == null || webProcess.HasExited) { Debug.LogWarning("ShowWindow: webview process not running"); return; } bool ok = WebViewWin32.TryShowWindow(webProcess, true); Debug.Log("ShowWindow clicked, result: " + ok); } /// /// Documentation text normalized. /// void ToggleTextVisibility() { if (controlText == null) { Debug.LogWarning("ToggleTextVisibility: controlText is not assigned"); return; } bool isCurrentlyActive = controlText.gameObject.activeSelf; controlText.gameObject.SetActive(!isCurrentlyActive); Debug.Log($"Text visibility toggled: {(isCurrentlyActive ? "hidden" : "shown")}"); } public void StartWebView() { LaunchWebView(); } public void StopWebView() { KillWebView(); } void OnAutostartToggleChanged(bool value) { autostart = value; PlayerPrefs.SetInt(PREF_AUTOSTART, autostart ? 1 : 0); PlayerPrefs.Save(); Debug.Log("Autostart toggled: " + autostart); } string GetExePath() { #if UNITY_EDITOR return Path.Combine(editorExeRoot, "fdBrowser.exe"); #else // Documentation text normalized. string streamingPath = Path.Combine(Application.streamingAssetsPath, "fdBrowser", "fdBrowser.exe"); if (File.Exists(streamingPath)) { Debug.Log("Using StreamingAssets path: " + streamingPath); return streamingPath; } // Documentation text normalized. 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; } /// /// Documentation text normalized. /// 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); } string GetDisplayUrl(string url) { if (string.IsNullOrEmpty(url)) return url; try { Uri uri = new Uri(url); if (uri.Scheme == Uri.UriSchemeFile) { string localPath = uri.LocalPath; string h5Folder = GetH5FolderPath(); if (localPath.StartsWith(h5Folder, StringComparison.OrdinalIgnoreCase)) { string relativePath = localPath.Substring(h5Folder.Length).TrimStart(Path.DirectorySeparatorChar); return $"localhost::{relativePath.Replace(Path.DirectorySeparatorChar, '/')}"; } } } catch (Exception e) { Debug.LogWarning("GetDisplayUrl: failed to parse url '" + url + "' : " + e.Message); } return url; } bool IsLikelyWebUrl(string url) { return Regex.IsMatch(url, @"^(https?:\/\/|www\.|[a-zA-Z0-9-]+\.[a-zA-Z]{2,})"); } string NormalizeToAbsolute(string url) { if (string.IsNullOrEmpty(url)) { Debug.LogWarning("NormalizeToAbsolute called with empty url"); return url; } try { if (Uri.TryCreate(url, UriKind.Absolute, out var parsed) && parsed.IsAbsoluteUri) { return parsed.AbsoluteUri; } if (url.StartsWith("localhost::")) { // Map localhost::display back to actual file URI string relativePath = url.Substring("localhost::".Length).Replace('/', Path.DirectorySeparatorChar); string h5Folder = GetH5FolderPath(); string fullPath = Path.Combine(h5Folder, relativePath); return new Uri(fullPath).AbsoluteUri; } // Treat as local file path string abs = Path.GetFullPath(url); return new Uri(abs).AbsoluteUri; // yields file:///C:/... } catch (Exception e) { Debug.LogWarning("NormalizeToAbsolute: failed to normalize url '" + url + "' : " + e.Message); return url; } } void LaunchWebViewWithUrl(string url) { string finalUrl = NormalizeToAbsolute(url); Debug.Log($"LaunchWebViewWithUrl requested. original='{url}', normalized='{finalUrl}'"); startUrl = finalUrl; // update input field display if (urlInputField != null) urlInputField.text = GetDisplayUrl(finalUrl); // 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: " + finalUrl); LaunchWebView(); return; } try { Debug.Log($"WebView process running. PID={webProcess.Id}, MainWindowHandle=0x{webProcess.MainWindowHandle.ToInt64():X}"); } catch { } // Documentation text normalized. bool sent = WebViewWin32.TrySendUrlToWindow(webProcess, finalUrl); Debug.Log($"TrySendUrlToWindow returned: {sent}"); if (sent) { Debug.Log("Sent URL to existing WebView process: " + finalUrl); // Notify followers that webview content may have changed and they should reapply positioning WebViewReady?.Invoke(); return; } // Documentation text normalized. Debug.Log("Failed to send URL to existing process, restarting to open: " + finalUrl); KillWebView(); LaunchWebView(); // after relaunch, WaitForMainWindowHandle will invoke WebViewReady when handle ready } }