// NOTE: Windows-only. Uses user32.dll P/Invoke, which does not compile on Android. // Wrapped in UNITY_STANDALONE_WIN so the Windows build/logic is unchanged while // Android simply compiles this class out. #if UNITY_STANDALONE_WIN using System; using System.Diagnostics; using System.Runtime.InteropServices; using UnityEngine; public static class WebViewWin32 { [DllImport("user32.dll")] static extern bool SetWindowPos( IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); [DllImport("user32.dll")] static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); [DllImport("user32.dll")] static extern bool ClientToScreen(IntPtr hWnd, ref POINT lpPoint); [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll")] private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); [StructLayout(LayoutKind.Sequential)] public struct RECT { public int Left; public int Top; public int Right; public int Bottom; } [StructLayout(LayoutKind.Sequential)] public struct POINT { public int X; public int Y; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct COPYDATASTRUCT { public IntPtr dwData; public int cbData; public IntPtr lpData; } // SetWindowPos flags const uint SWP_NOZORDER = 0x0004; const uint SWP_NOACTIVATE = 0x0010; const uint SWP_NOMOVE = 0x0002; const uint SWP_NOSIZE = 0x0001; // HWND insert-after special values static readonly IntPtr HWND_TOP = new IntPtr(0); static readonly IntPtr HWND_BOTTOM = new IntPtr(1); static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2); const uint WM_COPYDATA = 0x004A; // ShowWindow commands const int SW_HIDE = 0; const int SW_SHOW = 5; const int SW_MINIMIZE = 6; const int SW_RESTORE = 9; public static bool TrySetWindowRect(Process process, int x, int y, int width, int height) { if (process == null || process.HasExited) return false; if (process.MainWindowHandle == IntPtr.Zero) { process.Refresh(); if (process.MainWindowHandle == IntPtr.Zero) return false; } SetWindowPos( process.MainWindowHandle, IntPtr.Zero, x, y, width, height, SWP_NOZORDER | SWP_NOACTIVATE ); return true; } /// /// Place the given process window directly above the target process window in Z-order /// and set its position/size. This ensures the webview sits just above the Unity window. /// public static bool TrySetWindowRectAboveProcess(Process process, Process targetProcess, int x, int y, int width, int height) { if (process == null || process.HasExited || targetProcess == null || targetProcess.HasExited) return false; if (process.MainWindowHandle == IntPtr.Zero) { process.Refresh(); if (process.MainWindowHandle == IntPtr.Zero) return false; } if (targetProcess.MainWindowHandle == IntPtr.Zero) { targetProcess.Refresh(); if (targetProcess.MainWindowHandle == IntPtr.Zero) return false; } // Place process window after targetProcess window in Z-order (i.e. on top of target) // Do not use SWP_NOZORDER so that z-order is changed. bool ok = SetWindowPos( process.MainWindowHandle, targetProcess.MainWindowHandle, x, y, width, height, SWP_NOACTIVATE ); return ok; } public static bool TryGetWindowRect(Process process, out RECT rect) { rect = new RECT(); if (process == null || process.HasExited) return false; if (process.MainWindowHandle == IntPtr.Zero) { process.Refresh(); if (process.MainWindowHandle == IntPtr.Zero) return false; } return GetWindowRect(process.MainWindowHandle, out rect); } public static bool TryGetClientScreenRect(Process process, out RECT rect) { rect = new RECT(); if (process == null || process.HasExited) return false; if (process.MainWindowHandle == IntPtr.Zero) { process.Refresh(); if (process.MainWindowHandle == IntPtr.Zero) return false; } POINT point = new POINT { X = 0, Y = 0 }; if (!ClientToScreen(process.MainWindowHandle, ref point)) return false; rect.Left = point.X; rect.Top = point.Y; rect.Right = point.X + UnityEngine.Screen.width; rect.Bottom = point.Y + UnityEngine.Screen.height; return true; } /// /// Try to show or hide the given process main window. /// public static bool TryShowWindow(Process process, bool show) { if (process == null || process.HasExited) return false; if (process.MainWindowHandle == IntPtr.Zero) { process.Refresh(); if (process.MainWindowHandle == IntPtr.Zero) return false; } try { if (!show) { // Minimize instead of hide return ShowWindow(process.MainWindowHandle, SW_MINIMIZE); } else { // Restore and bring to top bool r = ShowWindow(process.MainWindowHandle, SW_RESTORE); try { // bring to top without making it topmost SetWindowPos(process.MainWindowHandle, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); } catch { } return r; } } catch (Exception e) { UnityEngine.Debug.LogWarning("TryShowWindow failed: " + e.Message); return false; } } /// /// Documentation text normalized. /// Documentation text normalized. /// Documentation text normalized. /// public static bool TrySendUrlToWindow(Process process, string url) { if (process == null || process.HasExited || string.IsNullOrEmpty(url)) return false; if (process.MainWindowHandle == IntPtr.Zero) { process.Refresh(); if (process.MainWindowHandle == IntPtr.Zero) return false; } IntPtr hWnd = process.MainWindowHandle; IntPtr ptrString = IntPtr.Zero; IntPtr ptrCds = IntPtr.Zero; try { // Allocate Unicode string (null-terminated) ptrString = Marshal.StringToHGlobalUni(url); var cds = new COPYDATASTRUCT { dwData = IntPtr.Zero, cbData = (url.Length + 1) * 2, lpData = ptrString }; int size = Marshal.SizeOf(typeof(COPYDATASTRUCT)); ptrCds = Marshal.AllocHGlobal(size); Marshal.StructureToPtr(cds, ptrCds, false); IntPtr res = SendMessage(hWnd, WM_COPYDATA, IntPtr.Zero, ptrCds); long resVal = res.ToInt64(); UnityEngine.Debug.Log($"WebViewWin32.TrySendUrlToWindow: Sent WM_COPYDATA to hWnd=0x{hWnd.ToInt64():X}, url={url}, SendMessage result={resVal}"); // Treat non-zero as handled/accepted by receiver. If receiver returns zero, consider not handled. return resVal != 0; } catch (Exception e) { UnityEngine.Debug.LogWarning("WebViewWin32.TrySendUrlToWindow exception: " + e.Message); return false; } finally { if (ptrCds != IntPtr.Zero) Marshal.FreeHGlobal(ptrCds); if (ptrString != IntPtr.Zero) Marshal.FreeHGlobal(ptrString); } } } #endif