716 lines
23 KiB
C#
716 lines
23 KiB
C#
using System.Diagnostics;
|
|
using System.IO;
|
|
using UnityEngine;
|
|
using System;
|
|
using System.Collections;
|
|
using UnityEngine.UI;
|
|
using UnityEngine.EventSystems;
|
|
using System.Text.RegularExpressions;
|
|
using UnityEngine.Networking;
|
|
using GameServer.Client;
|
|
|
|
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\fdGamer\build\windows\x64\runner\Release";
|
|
|
|
[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;
|
|
private const string FlutterExeName = "浮动小游戏.exe";
|
|
private const string LocalControlBaseUrl = "http://127.0.0.1:18961";
|
|
|
|
// 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, 0) == 1;
|
|
if (autostartToggle != null)
|
|
{
|
|
autostartToggle.isOn = autostart;
|
|
autostartToggle.onValueChanged.AddListener(OnAutostartToggleChanged);
|
|
}
|
|
|
|
// set default homepage if empty
|
|
if (string.IsNullOrEmpty(homepageUrl))
|
|
{
|
|
homepageUrl = string.Empty;
|
|
}
|
|
}
|
|
|
|
void Start()
|
|
{
|
|
if (controlText != null)
|
|
{
|
|
controlText.gameObject.SetActive(true);
|
|
}
|
|
if (urlInputField != null)
|
|
{
|
|
urlInputField.text = string.Empty;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
string GetServerBaseUrl()
|
|
{
|
|
if (NetworkManager.Instance != null && !string.IsNullOrWhiteSpace(NetworkManager.Instance.ServerUrl))
|
|
{
|
|
string raw = NetworkManager.Instance.ServerUrl.Trim();
|
|
if (Uri.TryCreate(raw, UriKind.Absolute, out Uri parsed))
|
|
{
|
|
return $"{parsed.Scheme}://{parsed.Authority}";
|
|
}
|
|
return raw.TrimEnd('/');
|
|
}
|
|
return "http://47.112.187.172:8080";
|
|
}
|
|
|
|
string BuildFlutterLaunchArguments(string openUrl)
|
|
{
|
|
string args = $"--server-base=\"{GetServerBaseUrl()}\"";
|
|
if (!string.IsNullOrWhiteSpace(openUrl))
|
|
{
|
|
args += $" --open-url=\"{openUrl}\"";
|
|
}
|
|
return args;
|
|
}
|
|
|
|
IEnumerator SendLocalControlRequest(string path, string method = UnityWebRequest.kHttpVerbGET, string jsonBody = null)
|
|
{
|
|
string url = $"{LocalControlBaseUrl}{path}";
|
|
using UnityWebRequest request = new UnityWebRequest(url, method);
|
|
request.downloadHandler = new DownloadHandlerBuffer();
|
|
if (!string.IsNullOrEmpty(jsonBody))
|
|
{
|
|
byte[] bodyRaw = System.Text.Encoding.UTF8.GetBytes(jsonBody);
|
|
request.uploadHandler = new UploadHandlerRaw(bodyRaw);
|
|
request.SetRequestHeader("Content-Type", "application/json");
|
|
}
|
|
yield return request.SendWebRequest();
|
|
if (request.result != UnityWebRequest.Result.Success)
|
|
{
|
|
Debug.LogWarning($"Local Flutter control request failed: {method} {url} -> {request.error}");
|
|
}
|
|
}
|
|
|
|
void SendLocalControlOpen(string normalizedUrl)
|
|
{
|
|
string escaped = normalizedUrl.Replace("\\", "\\\\").Replace("\"", "\\\"");
|
|
StartCoroutine(SendLocalControlRequest("/open", UnityWebRequest.kHttpVerbPOST, $"{{\"url\":\"{escaped}\"}}"));
|
|
}
|
|
|
|
void SendLocalControlCommand(string path)
|
|
{
|
|
StartCoroutine(SendLocalControlRequest(path));
|
|
}
|
|
|
|
void LaunchWebView()
|
|
{
|
|
LaunchWebViewWithUrl(string.Empty);
|
|
}
|
|
|
|
void LaunchWebViewWithUrlInternal(string openUrl)
|
|
{
|
|
string exePath = GetExePath();
|
|
|
|
Debug.Log("WebView exe path: " + exePath);
|
|
|
|
if (!File.Exists(exePath))
|
|
{
|
|
Debug.LogError("浮动小游戏主程序不存在: " + exePath);
|
|
return;
|
|
}
|
|
|
|
string args = BuildFlutterLaunchArguments(openUrl);
|
|
|
|
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("浮动小游戏已启动。PID: " + 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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Documentation text normalized.
|
|
/// Documentation text normalized.
|
|
/// </summary>
|
|
/// <param name="url"></param>
|
|
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 (webProcess == null || webProcess.HasExited)
|
|
{
|
|
Debug.Log("浮动小游戏未运行,直接启动并打开 URL: " + final);
|
|
LaunchWebViewWithUrlInternal(final);
|
|
return;
|
|
}
|
|
SendLocalControlOpen(final);
|
|
WebViewReady?.Invoke();
|
|
}
|
|
|
|
bool TrySendCommand(string cmd)
|
|
{
|
|
if (webProcess == null || webProcess.HasExited)
|
|
{
|
|
Debug.LogWarning("TrySendCommand: 浮动小游戏未运行");
|
|
return false;
|
|
}
|
|
switch (cmd)
|
|
{
|
|
case "CMD:REFRESH":
|
|
SendLocalControlCommand("/refresh");
|
|
return true;
|
|
case "CMD:BACK":
|
|
SendLocalControlCommand("/back");
|
|
return true;
|
|
case "CMD:FORWARD":
|
|
SendLocalControlCommand("/forward");
|
|
return true;
|
|
case "CMD:HOME":
|
|
SendLocalControlCommand("/home");
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// UI control methods
|
|
public void RefreshWebView()
|
|
{
|
|
if (TrySendCommand("CMD:REFRESH"))
|
|
{
|
|
return;
|
|
}
|
|
LaunchWebView();
|
|
}
|
|
|
|
public void CloseWebView()
|
|
{
|
|
Debug.Log("Closing webview process");
|
|
KillWebView();
|
|
}
|
|
|
|
public void RestartWebView()
|
|
{
|
|
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()
|
|
{
|
|
StartWebView();
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Documentation text normalized.
|
|
/// </summary>
|
|
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()
|
|
{
|
|
if (webProcess == null || webProcess.HasExited)
|
|
{
|
|
LaunchWebView();
|
|
return;
|
|
}
|
|
SendLocalControlCommand("/home");
|
|
WebViewReady?.Invoke();
|
|
}
|
|
|
|
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
|
|
string editorPath = Path.Combine(editorExeRoot, FlutterExeName);
|
|
if (File.Exists(editorPath))
|
|
{
|
|
return editorPath;
|
|
}
|
|
return Path.Combine(Application.streamingAssetsPath, "fdGamer", FlutterExeName);
|
|
#else
|
|
string streamingPath = Path.Combine(Application.streamingAssetsPath, "fdGamer", FlutterExeName);
|
|
if (File.Exists(streamingPath))
|
|
{
|
|
return streamingPath;
|
|
}
|
|
string gameRoot = Path.GetDirectoryName(Application.dataPath);
|
|
string fallbackPath = Path.Combine(gameRoot, "fdGamer", FlutterExeName);
|
|
return fallbackPath;
|
|
#endif
|
|
}
|
|
|
|
string GetH5FolderPath()
|
|
{
|
|
#if UNITY_EDITOR
|
|
return h5EditorPath;
|
|
#else
|
|
return Path.Combine(Application.streamingAssetsPath, "h5LG");
|
|
#endif
|
|
}
|
|
|
|
System.Collections.Generic.List<string> ScanH5Folders(string folderPath)
|
|
{
|
|
var result = new System.Collections.Generic.List<string>();
|
|
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, "<html><body><h1>" + folderName + "</h1></html>");
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Documentation text normalized.
|
|
/// </summary>
|
|
public bool TryUpdateWebViewRect(int x, int y, int w, int h)
|
|
{
|
|
return webProcess != null && !webProcess.HasExited;
|
|
}
|
|
|
|
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 (webProcess == null || webProcess.HasExited)
|
|
{
|
|
LaunchWebViewWithUrlInternal(finalUrl);
|
|
return;
|
|
}
|
|
SendLocalControlOpen(finalUrl);
|
|
WebViewReady?.Invoke();
|
|
}
|
|
}
|