using System; using System.Reflection; using Unity.Pipeline.Commands; using UnityEditor; using UnityEngine; namespace Unity.Pipeline.Editor.Commands { /// /// 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. /// 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; } } }