/* * UniWinCore.cs * * Author: Kirurobo http://twitter.com/kirurobo * License: MIT 许可证 */ using System; using System.Runtime.InteropServices; using AOT; using UnityEngine; using System.Text; #if UNITY_EDITOR using UnityEditor; #endif namespace Kirurobo { /// /// LibUniWinC 的原生插件包装 /// internal class UniWinCore : IDisposable { /// /// 仅 Windows 下的透明方法类型 /// public enum TransparentType : int { None = 0, Alpha = 1, ColorKey = 2, } /// /// 状态变更事件类型(实验性) /// [Flags] public enum WindowStateEventType : int { None = 0, StyleChanged = 1, Resized = 2, // 后续规格可能会有变更 TopMostEnabled = 16 + 1 + 8, TopMostDisabled = 16 + 1, BottomMostEnabled = 32 + 1 + 8, BottomMostDisabled = 32 + 1, WallpaperModeEnabled = 64 + 1 + 8, WallpaperModeDisabled = 64 + 1, }; #region Native functions protected class LibUniWinC { [UnmanagedFunctionPointer(CallingConvention.Winapi)] public delegate void StringCallback([MarshalAs(UnmanagedType.LPWStr)] string returnString); [UnmanagedFunctionPointer((CallingConvention.Winapi))] public delegate void IntCallback([MarshalAs(UnmanagedType.I4)] int value); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool IsActive(); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool IsTransparent(); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool IsBorderless(); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool IsTopmost(); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool IsBottommost(); [DllImport("LibUniWinC", CallingConvention=CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool IsMaximized(); [DllImport("LibUniWinC", CallingConvention=CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool IsFreePositioningEnabled(); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool AttachMyWindow(); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool AttachMyOwnerWindow(); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool AttachMyActiveWindow(); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool DetachWindow(); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] public static extern void Update(); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] public static extern void SetTransparent([MarshalAs(UnmanagedType.U1)] bool bEnabled); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] public static extern void SetBorderless([MarshalAs(UnmanagedType.U1)] bool bEnabled); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] public static extern void SetAlphaValue(float alpha); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] public static extern void SetClickThrough([MarshalAs(UnmanagedType.U1)] bool bEnabled); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] public static extern void SetTopmost([MarshalAs(UnmanagedType.U1)] bool bEnabled); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] public static extern void SetBottommost([MarshalAs(UnmanagedType.U1)] bool bEnabled); [DllImport("LibUniWinC", CallingConvention = CallingConvention.Winapi)] public static extern void SetMaximized([MarshalAs(UnmanagedType.U1)] bool bZoomed); [DllImport("LibUniWinC", CallingConvention = CallingConvention.Winapi)] public static extern void EnableFreePositioning([MarshalAs(UnmanagedType.U1)] bool bEnabled); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] public static extern void SetPosition(float x, float y); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetPosition(out float x, out float y); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] public static extern void SetSize(float x, float y); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetSize(out float x, out float y); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetClientSize(out float width, out float height); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetClientRectangle(out float x, out float y, out float width, out float height); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool RegisterDropFilesCallback([MarshalAs(UnmanagedType.FunctionPtr)] StringCallback callback); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool UnregisterDropFilesCallback(); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool RegisterMonitorChangedCallback([MarshalAs(UnmanagedType.FunctionPtr)] IntCallback callback); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool UnregisterMonitorChangedCallback(); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool RegisterWindowStyleChangedCallback([MarshalAs(UnmanagedType.FunctionPtr)] IntCallback callback); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool UnregisterWindowStyleChangedCallback(); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool SetAllowDrop([MarshalAs(UnmanagedType.U1)] bool enabled); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] public static extern int GetCurrentMonitor(); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] public static extern int GetMonitorCount(); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetMonitorRectangle(int index, out float x, out float y, out float width, out float height); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] public static extern void SetCursorPosition(float x, float y); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetCursorPosition(out float x, out float y); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] public static extern int GetMouseButtons(); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] public static extern int GetModifierKeys(); #region 仅适用于 Windows [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] public static extern void SetTransparentType(int type); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] public static extern void SetKeyColor(uint colorref); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] public static extern int GetDebugInfo(); [DllImport("LibUniWinC",CallingConvention=CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool AttachWindowHandle(IntPtr hWnd); #endregion } #endregion static string[] lastDroppedFiles; static bool wasDropped = false; static bool wasMonitorChanged = false; static bool wasWindowStyleChanged = false; static WindowStateEventType windowStateEventType = WindowStateEventType.None; #if UNITY_EDITOR /// /// 获取 Unity 编辑器窗口 /// /// /// public static EditorWindow GetGameView() { var assembly = typeof(EditorWindow).Assembly; var type = assembly.GetType("UnityEditor.GameView"); var gameView = EditorWindow.GetWindow(type); return gameView; } #endif /// /// 判断窗口是否已附加且可用 /// /// true 如果此实例处于活动状态;否则为 false public bool IsActive { get; private set; } = false; /// /// 判断附加窗口是否始终置顶 /// public bool IsTopmost { get { return (IsActive && _isTopmost); } } private bool _isTopmost = false; /// /// 判断附加窗口是否始终置底 /// public bool IsBottommost { get { return (IsActive && _isBottommost); } } private bool _isBottommost = false; /// /// 判断附加窗口是否透明 /// public bool IsTransparent { get { return (IsActive && _isTransparent); } } private bool _isTransparent = false; /// /// 判断附加窗口是否点击穿透(即不接收任何鼠标操作) /// public bool IsClickThrough { get { return (IsActive && _isClickThrough); } } private bool _isClickThrough = false; /// /// 判断附加窗口是否无边框(无标题栏和边框) /// public bool IsBorderless { get { return (IsActive && _isBorderless); } } private bool _isBorderless = false; /// /// 判断附加窗口是否可以自由定位(仅 macOS) /// public bool IsFreePositioningEnabled { get { return (IsActive && _isFreePositioningEnabled); } } private bool _isFreePositioningEnabled = false; /// /// Windows 下的透明方法类型 /// private TransparentType transparentType = TransparentType.Alpha; /// /// 当 transparentType 为 ColorKey 时用于透明的颜色 /// private Color32 keyColor = new Color32(1, 0, 1, 0); #region Constructor or destructor /// /// 窗口控制构造函数 /// public UniWinCore() { IsActive = false; } /// /// 析构函数 /// ~UniWinCore() { Dispose(); } /// /// 结束时的处理 /// public void Dispose() { // 由于最后恢复窗口状态会引起注意,所以特意不恢复,因此注释掉 //DetachWindow(); // 替代 DetachWindow() LibUniWinC.UnregisterDropFilesCallback(); LibUniWinC.UnregisterMonitorChangedCallback(); LibUniWinC.UnregisterWindowStyleChangedCallback(); } #endregion #region Callbacks /// /// 显示器或分辨率变化时的回调 /// 此处的处理保持最低限度,仅设置标志 /// /// [MonoPInvokeCallback(typeof(LibUniWinC.IntCallback))] private static void _monitorChangedCallback([MarshalAs(UnmanagedType.I4)] int monitorCount) { wasMonitorChanged = true; } /// /// 窗口样式、最大化、最小化等调用的回调 /// 此处的处理保持最低限度,仅设置标志 /// /// [MonoPInvokeCallback(typeof(LibUniWinC.IntCallback))] private static void _windowStyleChangedCallback([MarshalAs(UnmanagedType.I4)] int e) { wasWindowStyleChanged = true; windowStateEventType = (WindowStateEventType)e; } /// /// 文件或文件夹被拖放时调用的回调 /// 将字符串转换为数组,并设置标志位 /// /// [MonoPInvokeCallback(typeof(LibUniWinC.StringCallback))] private static void _dropFilesCallback([MarshalAs(UnmanagedType.LPWStr)] string paths) { // 将以 LF 分隔的字符串分割为路径数组 //char[] delimiters = { '\n', '\0' }; //string[] files = paths.Split(delimiters).Where(s => s != "").ToArray(); string[] files = parsePaths(paths); if (files.Length > 0) { lastDroppedFiles = new string[files.Length]; files.CopyTo(lastDroppedFiles, 0); wasDropped = true; } } /// /// 将双引号包围、LF(或null)分隔的字符串转换为数组并返回 /// /// /// internal static string[] parsePaths(string text) { System.Collections.Generic.List list = new System.Collections.Generic.List(); bool inEscaped = false; int len = text.Length; StringBuilder sb = new StringBuilder(); for (int i = 0; i < len; i++) { char c = text[i]; if (c == '"') { if (inEscaped) { if (((i + 1) < len) && text[i + 1] == '"') { i++; sb.Append(c); // 连续双引号视为一个双引号 continue; } } inEscaped = !inEscaped; // 非连续则切换是否在引号内 } else if (c == '\n') { if (inEscaped) { // 如果在引号内,则作为路径的一部分 sb.Append(c); } else { // 如果不在引号内,则作为分隔符,移到下一个路径 if (sb.Length > 0) { list.Add(sb.ToString()); //sb.Clear(); // for .NET 4 or later sb.Length = 0; // for .NET 2 } } } else if (c == '\0') { // 空字符始终作为分隔符,移到下一个路径 if (sb.Length > 0) { list.Add(sb.ToString()); //sb.Clear(); // for .NET 4 or later sb.Length = 0; // for .NET 2 } } else { sb.Append(c); } } if (sb.Length > 0) { list.Add(sb.ToString()); } // 移除空字符串元素 list.RemoveAll(v => v.Length == 0); return list.ToArray(); } #endregion #region Find, attach or detach /// /// 将窗口状态恢复至最初并从操作对象中解除 /// public void DetachWindow() { #if UNITY_EDITOR // 在编辑器中,窗口样式可能无法正确获取置顶状态, // 因此默认视为非置顶,在分离时强制取消置顶 EnableTopmost(false); #endif LibUniWinC.DetachWindow(); } /// /// 查找自己的窗口(如果游戏视图是独立窗口则查找它)并作为操作对象 /// /// public bool AttachMyWindow() { #if UNITY_EDITOR_WIN // 似乎没有可靠的方法获取游戏视图,因此给予焦点后立即获取活动窗口 var gameView = GetGameView(); if (gameView) { gameView.Focus(); LibUniWinC.AttachMyActiveWindow(); } #else LibUniWinC.AttachMyWindow(); #endif // 添加事件处理程序 LibUniWinC.RegisterDropFilesCallback(_dropFilesCallback); LibUniWinC.RegisterMonitorChangedCallback(_monitorChangedCallback); LibUniWinC.RegisterWindowStyleChangedCallback(_windowStyleChangedCallback); IsActive = LibUniWinC.IsActive(); return IsActive; } public bool AttachWindowHandle(IntPtr hWnd) { LibUniWinC.AttachWindowHandle(hWnd); IsActive = LibUniWinC.IsActive(); return IsActive; } /// /// 选择自己进程中当前活动的窗口 /// 编辑器情况下,窗口会关闭或停靠,因此在聚焦时调用 /// /// public bool AttachMyActiveWindow() { LibUniWinC.AttachMyActiveWindow(); IsActive = LibUniWinC.IsActive(); return IsActive; } #endregion #region About window status /// /// 定期调用以维持窗口样式 /// public void Update() { LibUniWinC.Update(); } string GetDebubgWindowSizeInfo() { float x, y, cx, cy; LibUniWinC.GetSize(out x, out y); LibUniWinC.GetClientSize(out cx, out cy); return $"W:{x},H:{y} CW:{cx},CH:{cy}"; } /// /// 设置/取消透明 /// /// public void EnableTransparent(bool isTransparent) { // 编辑器无法透明或边框与正常不同,因此跳过 #if !UNITY_EDITOR LibUniWinC.SetTransparent(isTransparent); LibUniWinC.SetBorderless(isTransparent); #endif this._isTransparent = isTransparent; } /// /// 设置窗口透明度 /// /// 0.0 - 1.0 public void SetAlphaValue(float alpha) { // Windows 编辑器下,一旦半透明化后显示不会更新,因此禁用。Mac 则没问题 #if !UNITY_EDITOR_WIN LibUniWinC.SetAlphaValue(alpha); #endif } /// /// 设置窗口 Z 顺序(是否置顶)。 /// /// 如果设为 true 则置顶。 public void EnableTopmost(bool isTopmost) { LibUniWinC.SetTopmost(isTopmost); this._isTopmost = isTopmost; this._isBottommost = false; // 互斥 } /// /// 设置窗口 Z 顺序(是否置底)。 /// /// 如果设为 true 则置底。 public void EnableBottommost(bool isBottommost) { LibUniWinC.SetBottommost(isBottommost); this._isBottommost = isBottommost; this._isTopmost = false; // 互斥 } /// /// 设置/取消点击穿透 /// /// public void EnableClickThrough(bool isThrough) { // 编辑器下点击穿透可能导致无法操作,因此跳过 #if !UNITY_EDITOR LibUniWinC.SetClickThrough(isThrough); #endif this._isClickThrough = isThrough; } /// /// 最大化窗口(Mac 上为缩放) /// 最大化后可能还会调整大小,目前可能无法可靠工作 /// public void SetZoomed(bool isZoomed) { LibUniWinC.SetMaximized(isZoomed); } /// /// 获取窗口是否已最大化(Mac 上为缩放) /// 最大化后可能还会调整大小,目前可能无法可靠工作 /// public bool GetZoomed() { return LibUniWinC.IsMaximized(); } /// /// 设置窗口位置。 /// /// 位置。 public void SetWindowPosition(Vector2 position) { LibUniWinC.SetPosition(position.x, position.y); } /// /// 获取窗口位置。 /// /// 位置。 public Vector2 GetWindowPosition() { Vector2 pos = Vector2.zero; LibUniWinC.GetPosition(out pos.x, out pos.y); return pos; } /// /// 设置窗口大小。 /// /// x 为宽度,y 为高度 public void SetWindowSize(Vector2 size) { LibUniWinC.SetSize(size.x, size.y); } /// /// 获取窗口大小。 /// /// x 为宽度,y 为高度 public Vector2 GetWindowSize() { Vector2 size = Vector2.zero; LibUniWinC.GetSize(out size.x, out size.y); return size; } /// /// 获取客户区大小。 /// /// x 为宽度,y 为高度 public Vector2 GetClientSize() { Vector2 size = Vector2.zero; LibUniWinC.GetClientSize(out size.x, out size.y); return size; } /// /// 获取客户区矩形。 /// /// x 为宽度,y 为高度 public Rect GetClientRectangle() { Vector2 pos = Vector2.zero; Vector2 size = Vector2.zero; LibUniWinC.GetClientRectangle(out pos.x, out pos.y, out size.x, out size.y); return new Rect(pos.x, pos.y, size.x, size.y); } #endregion #region File opening public void SetAllowDrop(bool enabled) { LibUniWinC.SetAllowDrop(enabled); } #endregion #region Event observers /// /// 检查文件拖放并取消拖放标志 /// /// /// 如果文件被拖放则返回 true public bool ObserveDroppedFiles(out string[] files) { files = lastDroppedFiles; if (!wasDropped || files == null) return false; wasDropped = false; return true; } /// /// 检查显示器数量或分辨率变化,并取消标志 /// /// 如果已变化则返回 true public bool ObserveMonitorChanged() { if (!wasMonitorChanged) return false; wasMonitorChanged = false; return true; } /// /// 检查窗口样式是否已变更,并取消标志 /// /// 如果窗口样式已变更则返回 true public bool ObserveWindowStyleChanged() { if (!wasWindowStyleChanged) return false; windowStateEventType = WindowStateEventType.None; wasWindowStyleChanged = false; return true; } /// /// 检查窗口样式是否已变更,并取消标志 /// /// 如果窗口样式已变更则返回 true public bool ObserveWindowStyleChanged(out WindowStateEventType type) { if (!wasWindowStyleChanged) { type = WindowStateEventType.None; return false; } type = windowStateEventType; windowStateEventType = WindowStateEventType.None; wasWindowStyleChanged = false; return true; } #endregion #region About mouse cursor /// /// 设置鼠标指针位置。 /// /// 位置。 public static void SetCursorPosition(Vector2 position) { LibUniWinC.SetCursorPosition(position.x, position.y); } /// /// 获取鼠标指针位置。 /// /// 位置。 public static Vector2 GetCursorPosition() { Vector2 pos = Vector2.zero; LibUniWinC.GetCursorPosition(out pos.x, out pos.y); return pos; } /// /// Get pressed mouse buttons. /// /// Bit flags of pressed buttons public static int GetMouseButtons() { return LibUniWinC.GetMouseButtons(); } /// /// 获取按下的修饰键。 /// /// 按下键的位标志 public static int GetModifierKeys() { return LibUniWinC.GetModifierKeys(); } // 未实现 public static bool GetCursorVisible() { return true; } #endregion #region for Windows only /// /// 指定透明方法(仅 Windows 支持) /// /// public void SetTransparentType(TransparentType type) { LibUniWinC.SetTransparentType((Int32)type); transparentType = type; } /// /// 单色透明时指定透明色(仅 Windows 支持) /// /// public void SetKeyColor(Color32 color) { LibUniWinC.SetKeyColor((UInt32)(color.b * 0x10000 + color.g * 0x100 + color.r)); keyColor = color; } #endregion #region for macOS only /// /// 设置/取消窗口的自由配置(仅 macOS 支持) /// /// public void EnableFreePositioning(bool enabled) { LibUniWinC.EnableFreePositioning(enabled); _isFreePositioningEnabled = LibUniWinC.IsFreePositioningEnabled(); } #endregion #region About monitors /// /// 获取窗口所在显示器的索引 /// /// 显示器索引 public int GetCurrentMonitor() { return LibUniWinC.GetCurrentMonitor(); } /// /// 获取已连接显示器的数量 /// /// 数量 public static int GetMonitorCount() { return LibUniWinC.GetMonitorCount(); } /// /// 获取显示器的位置和大小 /// /// /// /// /// public static bool GetMonitorRectangle(int index, out Vector2 position, out Vector2 size) { return LibUniWinC.GetMonitorRectangle(index, out position.x, out position.y, out size.x, out size.y); } /// /// 将窗口适配到指定显示器 /// /// /// public bool FitToMonitor(int monitorIndex) { float dx, dy, dw, dh; if (LibUniWinC.GetMonitorRectangle(monitorIndex, out dx, out dy, out dw, out dh)) { // 如果处于最大化状态则先恢复 if (LibUniWinC.IsMaximized()) LibUniWinC.SetMaximized(false); // 指定显示器的中心坐标 float cx = dx + (dw / 2); float cy = dy + (dh / 2); // 将窗口中心移动到指定显示器中心 float ww, wh; LibUniWinC.GetSize(out ww, out wh); float wx = cx - (ww / 2); float wy = cy - (wh / 2); LibUniWinC.SetPosition(wx, wy); // 最大化 LibUniWinC.SetMaximized(true); //Debug.Log(String.Format("显示器 {4} : {0},{1} - {2},{3}", dx, dy, dw, dh, monitorIndex)); return true; } return false; } /// /// 打印显示器列表 /// [Obsolete] public static void DebugMonitorInfo() { int monitors = LibUniWinC.GetMonitorCount(); int currentMonitorIndex = LibUniWinC.GetCurrentMonitor(); string message = "当前显示器: " + currentMonitorIndex + "\r\n"; for (int i = 0; i < monitors; i++) { float x, y, w, h; bool result = LibUniWinC.GetMonitorRectangle(i, out x, out y, out w, out h); message += String.Format( "显示器 {0}: X:{1}, Y:{2} - W:{3}, H:{4}\r\n", i, x, y, w, h ); } Debug.Log(message); } /// /// 获取用于调试的信息 /// /// [Obsolete] public static int GetDebugInfo() { return LibUniWinC.GetDebugInfo(); } #endregion } }