Files

119 lines
4.5 KiB
C#

using System;
using System.Reflection;
using Unity.Pipeline.Commands;
using UnityEditor;
using UnityEngine;
namespace Unity.Pipeline.Editor.Commands
{
/// <summary>
/// Brings the Unity Editor application to the foreground (and restores it if minimized) by
/// focusing the main window's HostView.
///
/// ContainerWindow / HostView / GUIView are internal, so this uses reflection. GUIView.Focus()
/// maps to the native MonoGUIView::Focus, which activates the OS window. Useful for headless
/// automation: the editor throttles/defers some work (notably script compilation) while
/// unfocused, so a client can call this to bring it forward first.
/// </summary>
public static class FocusEditorCommand
{
const BindingFlags k_All = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
static Type s_ContainerWindowType;
static Type s_HostViewType;
static MethodInfo s_IsMainWindow;
static MethodInfo s_GuiViewFocus;
static bool s_Resolved;
[CliCommand("editor_focus", "Bring the Unity Editor window to the foreground", MainThreadRequired = true)]
public static string FocusEditor()
{
if (!ResolveReflection(out var error))
return error;
try
{
object mainWindow = null;
foreach (var w in Resources.FindObjectsOfTypeAll(s_ContainerWindowType))
{
if ((bool)s_IsMainWindow.Invoke(w, null)) { mainWindow = w; break; }
}
if (mainWindow == null)
return "Error: main editor window not found";
var rootView = GetRootView(mainWindow);
if (rootView == null)
return "Error: main window has no root view";
var host = FindHostView(rootView);
if (host == null)
return "Error: no HostView found in main window";
s_GuiViewFocus.Invoke(host, null);
return $"Editor focused via {host.GetType().Name}";
}
catch (Exception ex)
{
return $"Error: failed to focus editor: {ex.Message}";
}
}
static bool ResolveReflection(out string error)
{
error = null;
if (s_Resolved) return true;
var asm = typeof(EditorWindow).Assembly;
s_ContainerWindowType = asm.GetType("UnityEditor.ContainerWindow");
s_HostViewType = asm.GetType("UnityEditor.HostView");
var guiViewType = asm.GetType("UnityEditor.GUIView");
if (s_ContainerWindowType == null || s_HostViewType == null || guiViewType == null)
{
error = "Error: internal editor window types not found (Unity version mismatch?)";
return false;
}
s_IsMainWindow = s_ContainerWindowType.GetMethod("IsMainWindow", k_All);
s_GuiViewFocus = guiViewType.GetMethod("Focus", k_All, null, Type.EmptyTypes, null);
if (s_IsMainWindow == null || s_GuiViewFocus == null)
{
error = "Error: ContainerWindow.IsMainWindow or GUIView.Focus not found (internal API changed?)";
return false;
}
s_Resolved = true;
return true;
}
static object GetRootView(object mainWindow)
{
var prop = s_ContainerWindowType.GetProperty("rootView", k_All);
if (prop != null)
return prop.GetValue(mainWindow);
return s_ContainerWindowType.GetField("m_RootView", k_All)?.GetValue(mainWindow);
}
static object FindHostView(object rootView)
{
if (s_HostViewType.IsInstanceOfType(rootView))
return rootView;
if (!(rootView.GetType().GetProperty("allChildren", k_All)?.GetValue(rootView) is Array allChildren))
return null;
// Prefer a DockArea (an actual docked panel) over the Toolbar, but any HostView works
// to bring the main window — and thus the application — to the foreground.
object firstHost = null;
foreach (var c in allChildren)
{
if (c == null || !s_HostViewType.IsInstanceOfType(c)) continue;
if (firstHost == null) firstHost = c;
if (c.GetType().Name == "DockArea") return c;
}
return firstHost;
}
}
}