s&box Package Code Search

Search C# source code, UI razor templates, shaders, and configs across s&box packages.

Showing code results for query: * (1 total matches found)
flyinglizard.windowdock / Editor/WindowDock.cs
Editor library
using Editor;
using Sandbox;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;

namespace WindowDock;

/// <summary>
/// Editor dock that hosts another application's window. Pick a window from the list and
/// Dock reparents it into the panel (Win32 SetParent); Release hands it back to the desktop
/// with its original frame. Docked windows are released automatically when the editor exits.
/// </summary>
[Dock( "Editor", "Window Dock", "picture_in_picture" )]
public class ExternalWindowDock : Widget
{
	readonly ComboBox windowList;
	readonly Button dockButton;
	readonly Button releaseButton;
	readonly Label status;
	readonly HostWidget host;

	List<IntPtr> listedWindows = new();

	// The docked window and what we need to put it back the way we found it.
	IntPtr docked;
	long savedStyle;
	long savedExStyle;
	Win32.RECT savedRect;
	IntPtr hostHandle;

	// The last released window, watched briefly in case it ends up child-styled again.
	IntPtr released;
	long releasedStyle;
	RealTimeSince sinceReleased;

	bool mouseWasDown;
	RealTimeSince sinceClickedInside = 100;

	public ExternalWindowDock( Widget parent ) : base( parent )
	{
		Layout = Layout.Column();

		var bar = Layout.AddRow();
		bar.Margin = 4;
		bar.Spacing = 4;

		windowList = bar.Add( new ComboBox( this ), 1 );
		windowList.ToolTip = "Open windows (title — process)";

		var refresh = bar.Add( new IconButton( "refresh", RefreshList, this ) );
		refresh.ToolTip = "Refresh the window list";

		dockButton = bar.Add( new Button( "Dock", "input", this ) );
		dockButton.Clicked = DockSelected;

		releaseButton = bar.Add( new Button( "Release", "output", this ) );
		releaseButton.Clicked = Release;

		var add = bar.Add( new IconButton( "add", OpenAnotherPanel, this ) );
		add.ToolTip = "Open another Window Dock panel";

		// Keystrokes meant for the docked app must never land on these (a stray space would press Dock).
		foreach ( var control in new Widget[] { windowList, refresh, dockButton, releaseButton, add } )
			control.FocusMode = FocusMode.None;

		host = Layout.Add( new HostWidget( this ), 1 );
		host.Resized = FitToHost;

		status = host.Layout.Add( new Label( "Pick a window and press Dock.", host ) );
		status.Alignment = TextFlag.Center;

		RefreshList();
		UpdateButtons();
	}

	void RefreshList()
	{
		listedWindows = Win32.ListDockableWindows( docked );
		windowList.Clear();

		foreach ( var hwnd in listedWindows )
			windowList.AddItem( Win32.Describe( hwnd ) );

		if ( listedWindows.Count == 0 )
			windowList.AddItem( "No dockable windows found", enabled: false );
	}

	/// <summary>
	/// Extra panels are registered with the dock manager at runtime, so they get a View menu entry
	/// for this session but aren't recreated after an editor restart.
	/// </summary>
	void OpenAnotherPanel()
	{
		var manager = EditorWindow.DockManager;

		var number = 2;
		while ( manager.FindDockWidget( $"Window Dock {number}" ) is not null )
			number++;

		var panel = manager.AddDock( $"Window Dock {number}", "picture_in_picture", new ExternalWindowDock( null ), DockArea.Center, manager.FindDockWidget( this ) );
		panel.SetAsCurrentTab();
	}

	void DockSelected()
	{
		// One window per panel; Release first (or use +) so a click can't silently swap it out.
		if ( docked != IntPtr.Zero )
			return;

		var index = windowList.CurrentIndex;
		if ( index < 0 || index >= listedWindows.Count )
			return;

		var target = listedWindows[index];
		if ( !Win32.IsWindow( target ) )
		{
			SetStatus( "That window has closed." );
			RefreshList();
			return;
		}

		hostHandle = NativeHandle( host );
		if ( hostHandle == IntPtr.Zero )
		{
			SetStatus( "Couldn't get the panel's native window handle (s&box internals changed?)." );
			return;
		}

		// Restore a maximized/minimized window first so the saved rect is its normal size.
		if ( Win32.IsZoomed( target ) || Win32.IsIconic( target ) )
			Win32.ShowWindow( target, Win32.SW_RESTORE );

		savedStyle = Win32.TopLevelStyle( Win32.GetWindowLong( target, Win32.GWL_STYLE ) );
		savedExStyle = Win32.GetWindowLong( target, Win32.GWL_EXSTYLE );
		Win32.GetWindowRect( target, out savedRect );

		// Make sure Qt doesn't paint over the hosted window.
		Win32.SetWindowLong( hostHandle, Win32.GWL_STYLE, Win32.GetWindowLong( hostHandle, Win32.GWL_STYLE ) | Win32.WS_CLIPCHILDREN );

		// Win32 wants WS_CHILD set (and WS_POPUP cleared) before SetParent.
		var style = (savedStyle & ~Win32.FrameStyles) | Win32.WS_CHILD | Win32.WS_VISIBLE;
		var exStyle = savedExStyle & ~Win32.FrameExStyles;
		Win32.SetWindowLong( target, Win32.GWL_STYLE, style );
		Win32.SetWindowLong( target, Win32.GWL_EXSTYLE, exStyle );

		if ( Win32.SetParent( target, hostHandle ) == IntPtr.Zero && Marshal.GetLastWin32Error() != 0 )
		{
			var error = Marshal.GetLastWin32Error();
			Win32.SetWindowLong( target, Win32.GWL_STYLE, savedStyle );
			Win32.SetWindowLong( target, Win32.GWL_EXSTYLE, savedExStyle );
			Win32.SetWindowPos( target, IntPtr.Zero, 0, 0, 0, 0, Win32.SWP_NOMOVE | Win32.SWP_NOSIZE | Win32.SWP_NOZORDER | Win32.SWP_FRAMECHANGED );
			SetStatus( $"Windows refused to dock that window (error {error})." );
			return;
		}

		docked = target;
		status.Visible = false;
		FitToHost();
		Win32.ShowWindow( docked, Win32.SW_SHOW );
		Win32.SetFocus( docked );

		Log.Info( $"Window Dock: docked {Win32.Describe( docked )}" );
		RefreshList();
		UpdateButtons();
	}

	void Release() => Release( true );

	void Release( bool updateUi )
	{
		if ( docked == IntPtr.Zero )
			return;

		var window = docked;
		docked = IntPtr.Zero;

		if ( Win32.IsWindow( window ) )
		{
			Win32.SetParent( window, IntPtr.Zero );
			Win32.SetWindowLong( window, Win32.GWL_STYLE, savedStyle );
			Win32.SetWindowLong( window, Win32.GWL_EXSTYLE, savedExStyle );
			Win32.SetWindowPos( window, IntPtr.Zero, savedRect.Left, savedRect.Top,
				savedRect.Right - savedRect.Left, savedRect.Bottom - savedRect.Top,
				Win32.SWP_NOZORDER | Win32.SWP_FRAMECHANGED | Win32.SWP_SHOWWINDOW );

			// Docking joined the app's input queue to the editor's, and un-parenting doesn't split
			// them again; while they stay joined the app can't take keyboard input on its own.
			Win32.DetachInput( window, hostHandle );
			Win32.SetForegroundWindow( window );

			released = window;
			releasedStyle = savedStyle;
			sinceReleased = 0;
		}

		if ( !updateUi )
			return;

		SetStatus( "Pick a window and press Dock." );
		RefreshList();
		UpdateButtons();
	}

	void FitToHost()
	{
		if ( docked == IntPtr.Zero )
			return;

		if ( hostHandle == IntPtr.Zero || !Win32.GetClientRect( hostHandle, out var client ) )
			return;

		Win32.MoveWindow( docked, 0, 0, client.Right, client.Bottom, true );
	}

	/// <summary>
	/// Apps that draw their own title bar (Electron, Chromium) drag and resize themselves, and their
	/// minimize/restore buttons still work. Keep the window filling the panel whatever it does.
	/// </summary>
	void HoldInPlace()
	{
		if ( Win32.IsIconic( docked ) )
			Win32.ShowWindow( docked, Win32.SW_RESTORE );

		// Ends the drag/resize loop the app started from its title bar.
		if ( Win32.IsInMoveSize( docked ) )
			Win32.PostMessage( docked, Win32.WM_CANCELMODE, IntPtr.Zero, IntPtr.Zero );

		Win32.GetWindowRect( docked, out var window );
		Win32.GetWindowRect( hostHandle, out var panel );
		if ( window.Left != panel.Left || window.Top != panel.Top || window.Right != panel.Right || window.Bottom != panel.Bottom )
			FitToHost();
	}

	/// <summary>
	/// Clicking the docked window activates the editor, and Qt then pulls keyboard focus back to
	/// itself. For a moment after a click inside, hand focus back to the docked window.
	/// Clicking back into the editor doesn't return focus by itself: Qt still thinks the widget it
	/// last focused (e.g. the game viewport) has it, so it never asks Windows for it. Do that for Qt.
	/// </summary>
	void FocusOnClick()
	{
		var down = Win32.AnyMouseButtonDown();
		if ( down && !mouseWasDown )
		{
			if ( Win32.IsUnderCursor( docked ) )
			{
				sinceClickedInside = 0;
			}
			else
			{
				sinceClickedInside = 100;

				if ( Win32.HasFocus( docked ) )
					Win32.FocusEditorWindowUnderCursor();
			}
		}

		mouseWasDown = down;

		if ( sinceClickedInside < 0.3f && !Win32.HasFocus( docked ) )
			Win32.SetFocus( docked );
	}

	void SetStatus( string text )
	{
		status.Text = text;
		status.Visible = docked == IntPtr.Zero;
	}

	void UpdateButtons()
	{
		dockButton.Enabled = docked == IntPtr.Zero;
		releaseButton.Enabled = docked != IntPtr.Zero;
	}

	[EditorEvent.Frame]
	void KeepDocked()
	{
		GuardReleased();

		if ( docked == IntPtr.Zero )
			return;

		// The hosted app closed its window.
		if ( !Win32.IsWindow( docked ) )
		{
			docked = IntPtr.Zero;
			SetStatus( "The docked window was closed." );
			RefreshList();
			UpdateButtons();
			return;
		}

		// A hotload adds new fields at their default.
		if ( hostHandle == IntPtr.Zero )
			hostHandle = NativeHandle( host );

		HoldInPlace();
		FocusOnClick();
	}

	/// <summary>
	/// A top-level window that still has WS_CHILD can't be activated: it takes no typing and stays
	/// behind everything. Some apps re-apply the style they last saw, so watch a released window briefly.
	/// </summary>
	void GuardReleased()
	{
		if ( released == IntPtr.Zero )
			return;

		if ( sinceReleased > 3 || !Win32.IsWindow( released ) )
		{
			released = IntPtr.Zero;
			return;
		}

		var style = Win32.GetWindowLong( released, Win32.GWL_STYLE );
		if ( (style & Win32.WS_CHILD) == 0 || !Win32.IsTopLevel( released ) )
			return;

		Log.Warning( $"Window Dock: {Win32.Describe( released )} went back to a child style after release (0x{style:X8}); restoring 0x{releasedStyle:X8}" );
		Win32.SetWindowLong( released, Win32.GWL_STYLE, releasedStyle );
		Win32.SetWindowPos( released, IntPtr.Zero, 0, 0, 0, 0, Win32.SWP_NOMOVE | Win32.SWP_NOSIZE | Win32.SWP_NOZORDER | Win32.SWP_FRAMECHANGED );
	}

	// Destroying the editor's windows would take a docked window down with them,
	// so hand it back while ours still exist.
	[Event( "app.exit" )]
	void ReleaseOnExit() => Release( false );

	// Best effort: by now Qt has usually destroyed our native window (and the child with it).
	public override void OnDestroyed()
	{
		Release( false );
		base.OnDestroyed();
	}

	/// <summary>
	/// Editor.Widget keeps its QWidget internal; read it and ask Qt for the HWND (this also makes the widget native).
	/// </summary>
	static IntPtr NativeHandle( Widget widget )
	{
		var field = typeof( Widget ).GetField( "_widget", BindingFlags.Instance | BindingFlags.NonPublic );
		var native = field?.GetValue( widget );
		var winId = native?.GetType().GetMethod( "winId", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, Type.EmptyTypes );

		return winId?.Invoke( native, null ) is IntPtr handle ? handle : IntPtr.Zero;
	}

	class HostWidget : Widget
	{
		public Action Resized;

		public HostWidget( Widget parent ) : base( parent )
		{
			Layout = Layout.Column();
			SetSizeMode( SizeMode.Flexible, SizeMode.Flexible );
		}

		protected override void OnResize()
		{
			base.OnResize();
			Resized?.Invoke();
		}
	}

	static class Win32
	{
		public const int GWL_STYLE = -16;
		public const int GWL_EXSTYLE = -20;

		public const long WS_CHILD = 0x40000000;
		public const long WS_VISIBLE = 0x10000000;
		public const long WS_CLIPCHILDREN = 0x02000000;
		const long WS_POPUP = 0x80000000;
		const long WS_CAPTION = 0x00C00000;
		const long WS_THICKFRAME = 0x00040000;
		const long WS_SYSMENU = 0x00080000;
		const long WS_MINIMIZEBOX = 0x00020000;
		const long WS_MAXIMIZEBOX = 0x00010000;
		public const long FrameStyles = WS_POPUP | WS_CAPTION | WS_THICKFRAME | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;

		const long WS_EX_DLGMODALFRAME = 0x00000001;
		const long WS_EX_TOOLWINDOW = 0x00000080;
		const long WS_EX_WINDOWEDGE = 0x00000100;
		const long WS_EX_CLIENTEDGE = 0x00000200;
		const long WS_EX_STATICEDGE = 0x00020000;
		const long WS_EX_APPWINDOW = 0x00040000;
		public const long FrameExStyles = WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE | WS_EX_APPWINDOW;

		public const int SW_SHOW = 5;
		public const int SW_RESTORE = 9;

		public const uint SWP_NOSIZE = 0x0001;
		public const uint SWP_NOMOVE = 0x0002;
		public const uint SWP_NOZORDER = 0x0004;
		public const uint SWP_FRAMECHANGED = 0x0020;
		public const uint SWP_SHOWWINDOW = 0x0040;

		public const uint WM_CANCELMODE = 0x001F;

		const uint GW_OWNER = 4;
		const int DWMWA_CLOAKED = 14;
		const int GUI_INMOVESIZE = 0x0002;

		[StructLayout( LayoutKind.Sequential )]
		public struct RECT { public int Left, Top, Right, Bottom; }

		[StructLayout( LayoutKind.Sequential )]
		struct POINT { public int X, Y; }

		[StructLayout( LayoutKind.Sequential )]
		struct GUITHREADINFO
		{
			public int cbSize;
			public int flags;
			public IntPtr hwndActive, hwndFocus, hwndCapture, hwndMenuOwner, hwndMoveSize, hwndCaret;
			public RECT rcCaret;
		}

		delegate bool EnumWindowsProc( IntPtr hwnd, IntPtr lParam );

		[DllImport( "user32.dll" )] static extern bool EnumWindows( EnumWindowsProc callback, IntPtr lParam );
		[DllImport( "user32.dll" )] static extern bool IsWindowVisible( IntPtr hwnd );
		[DllImport( "user32.dll" )] static extern IntPtr GetWindow( IntPtr hwnd, uint cmd );
		[DllImport( "user32.dll" )] static extern int GetWindowTextLength( IntPtr hwnd );
		[DllImport( "user32.dll", CharSet = CharSet.Unicode )] static extern int GetWindowText( IntPtr hwnd, StringBuilder text, int max );
		[DllImport( "user32.dll", CharSet = CharSet.Unicode )] static extern int GetClassName( IntPtr hwnd, StringBuilder text, int max );
		[DllImport( "user32.dll" )] static extern uint GetWindowThreadProcessId( IntPtr hwnd, out uint processId );
		[DllImport( "user32.dll", EntryPoint = "GetWindowLongPtrW" )] static extern IntPtr GetWindowLongPtr( IntPtr hwnd, int index );
		[DllImport( "user32.dll", EntryPoint = "SetWindowLongPtrW" )] static extern IntPtr SetWindowLongPtr( IntPtr hwnd, int index, IntPtr value );
		[DllImport( "dwmapi.dll" )] static extern int DwmGetWindowAttribute( IntPtr hwnd, int attribute, out int value, int size );

		[DllImport( "user32.dll" )] public static extern bool IsWindow( IntPtr hwnd );
		[DllImport( "user32.dll" )] public static extern bool IsZoomed( IntPtr hwnd );
		[DllImport( "user32.dll" )] public static extern bool IsIconic( IntPtr hwnd );
		[DllImport( "user32.dll" )] public static extern bool ShowWindow( IntPtr hwnd, int cmd );
		[DllImport( "user32.dll" )] public static extern bool GetWindowRect( IntPtr hwnd, out RECT rect );
		[DllImport( "user32.dll", SetLastError = true )] public static extern IntPtr SetParent( IntPtr child, IntPtr parent );
		[DllImport( "user32.dll" )] public static extern bool MoveWindow( IntPtr hwnd, int x, int y, int width, int height, bool repaint );
		[DllImport( "user32.dll" )] public static extern bool SetWindowPos( IntPtr hwnd, IntPtr after, int x, int y, int width, int height, uint flags );
		[DllImport( "user32.dll" )] public static extern bool SetForegroundWindow( IntPtr hwnd );
		[DllImport( "user32.dll" )] public static extern bool GetClientRect( IntPtr hwnd, out RECT rect );
		[DllImport( "user32.dll" )] public static extern bool PostMessage( IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam );
		[DllImport( "user32.dll" )] public static extern IntPtr SetFocus( IntPtr hwnd );
		[DllImport( "user32.dll" )] static extern IntPtr GetFocus();
		[DllImport( "user32.dll" )] static extern bool IsChild( IntPtr parent, IntPtr hwnd );
		[DllImport( "user32.dll" )] static extern bool GetCursorPos( out POINT point );
		[DllImport( "user32.dll" )] static extern IntPtr WindowFromPoint( POINT point );
		[DllImport( "user32.dll" )] static extern short GetAsyncKeyState( int key );
		[DllImport( "user32.dll" )] static extern bool GetGUIThreadInfo( uint threadId, ref GUITHREADINFO info );

		static bool IsSelfOrChild( IntPtr window, IntPtr hwnd ) => hwnd != IntPtr.Zero && (hwnd == window || IsChild( window, hwnd ));

		public static bool HasFocus( IntPtr window ) => IsSelfOrChild( window, GetFocus() );

		public static bool IsUnderCursor( IntPtr window ) => GetCursorPos( out var point ) && IsSelfOrChild( window, WindowFromPoint( point ) );

		/// <summary>
		/// Give keyboard focus to the editor window under the cursor (the game viewport, the main window...).
		/// Clicks on other apps or other docked windows are left alone.
		/// </summary>
		public static void FocusEditorWindowUnderCursor()
		{
			if ( !GetCursorPos( out var point ) )
				return;

			var hwnd = WindowFromPoint( point );
			if ( hwnd == IntPtr.Zero )
				return;

			GetWindowThreadProcessId( hwnd, out var pid );
			if ( pid == (uint)Environment.ProcessId )
				SetFocus( hwnd );
		}

		// Left, right and middle buttons.
		public static bool AnyMouseButtonDown() => ((GetAsyncKeyState( 0x01 ) | GetAsyncKeyState( 0x02 ) | GetAsyncKeyState( 0x04 )) & 0x8000) != 0;

		[DllImport( "user32.dll" )] static extern bool AttachThreadInput( uint attach, uint attachTo, bool join );
		[DllImport( "user32.dll" )] static extern IntPtr GetAncestor( IntPtr hwnd, uint flags );
		[DllImport( "user32.dll" )] static extern IntPtr GetDesktopWindow();

		const long OverlappedFrame = WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;

		public static bool IsTopLevel( IntPtr hwnd ) => GetAncestor( hwnd, 1 ) == GetDesktopWindow();

		/// <summary>
		/// The style to hand a window back with. A window docked while already child-styled (left broken
		/// by an earlier release) gets a normal overlapped frame instead of its broken style.
		/// </summary>
		public static long TopLevelStyle( long style ) => (style & WS_CHILD) == 0 ? style : (style & ~WS_CHILD) | OverlappedFrame;

		public static void DetachInput( IntPtr window, IntPtr editorWindow )
		{
			var appThread = GetWindowThreadProcessId( window, out _ );
			var editorThread = GetWindowThreadProcessId( editorWindow, out _ );
			if ( appThread == 0 || editorThread == 0 || appThread == editorThread )
				return;

			AttachThreadInput( appThread, editorThread, false );
			AttachThreadInput( editorThread, appThread, false );
		}

		public static bool IsInMoveSize( IntPtr window )
		{
			var info = new GUITHREADINFO { cbSize = Marshal.SizeOf<GUITHREADINFO>() };
			return GetGUIThreadInfo( GetWindowThreadProcessId( window, out _ ), ref info )
				&& (info.flags & GUI_INMOVESIZE) != 0 && IsSelfOrChild( window, info.hwndMoveSize );
		}

		public static long GetWindowLong( IntPtr hwnd, int index ) => GetWindowLongPtr( hwnd, index ).ToInt64();
		public static void SetWindowLong( IntPtr hwnd, int index, long value ) => SetWindowLongPtr( hwnd, index, new IntPtr( value ) );

		/// <summary>
		/// Visible, titled, unowned top-level windows from other processes - roughly what the taskbar shows.
		/// </summary>
		public static List<IntPtr> ListDockableWindows( IntPtr include )
		{
			var ownProcess = (uint)Environment.ProcessId;
			var windows = new List<IntPtr>();

			EnumWindows( ( hwnd, _ ) =>
			{
				if ( hwnd == include )
					return true;

				if ( !IsWindowVisible( hwnd ) || GetWindowTextLength( hwnd ) == 0 || GetWindow( hwnd, GW_OWNER ) != IntPtr.Zero )
					return true;

				if ( (GetWindowLong( hwnd, GWL_EXSTYLE ) & WS_EX_TOOLWINDOW) != 0 )
					return true;

				GetWindowThreadProcessId( hwnd, out var pid );
				if ( pid == ownProcess )
					return true;

				// Suspended/background UWP frames report visible but are cloaked.
				if ( DwmGetWindowAttribute( hwnd, DWMWA_CLOAKED, out var cloaked, sizeof( int ) ) == 0 && cloaked != 0 )
					return true;

				// UWP/WinUI hosts (Calculator, Settings, Terminal...) can't be reparented properly.
				var className = ClassName( hwnd );
				if ( className is "ApplicationFrameWindow" or "Windows.UI.Core.CoreWindow" or "Progman" )
					return true;

				windows.Add( hwnd );
				return true;
			}, IntPtr.Zero );

			return windows;
		}

		public static string Describe( IntPtr hwnd )
		{
			var title = new StringBuilder( GetWindowTextLength( hwnd ) + 1 );
			GetWindowText( hwnd, title, title.Capacity );

			GetWindowThreadProcessId( hwnd, out var pid );
			string process;
			try { process = Process.GetProcessById( (int)pid ).ProcessName; }
			catch { process = $"pid {pid}"; }

			return $"{title} — {process}";
		}

		static string ClassName( IntPtr hwnd )
		{
			var name = new StringBuilder( 256 );
			GetClassName( hwnd, name, name.Capacity );
			return name.ToString();
		}
	}
}
Debug: View Raw JSON Response
{
    "TotalCount": 1,
    "Files": [
        {
            "Ident": "flyinglizard.windowdock",
            "Path": "Editor/WindowDock.cs",
            "FileName": "WindowDock.cs",
            "PackageType": "library",
            "CodeKind": "Editor",
            "AssetVersionId": 423791,
            "IsPrivate": false,
            "Code": "using Editor;\nusing Sandbox;\nusing System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Reflection;\nusing System.Runtime.InteropServices;\nusing System.Text;\n\nnamespace WindowDock;\n\n/// <summary>\n/// Editor dock that hosts another application's window. Pick a window from the list and\n/// Dock reparents it into the panel (Win32 SetParent); Release hands it back to the desktop\n/// with its original frame. Docked windows are released automatically when the editor exits.\n/// </summary>\n[Dock( \"Editor\", \"Window Dock\", \"picture_in_picture\" )]\npublic class ExternalWindowDock : Widget\n{\n\treadonly ComboBox windowList;\n\treadonly Button dockButton;\n\treadonly Button releaseButton;\n\treadonly Label status;\n\treadonly HostWidget host;\n\n\tList<IntPtr> listedWindows = new();\n\n\t// The docked window and what we need to put it back the way we found it.\n\tIntPtr docked;\n\tlong savedStyle;\n\tlong savedExStyle;\n\tWin32.RECT savedRect;\n\tIntPtr hostHandle;\n\n\t// The last released window, watched briefly in case it ends up child-styled again.\n\tIntPtr released;\n\tlong releasedStyle;\n\tRealTimeSince sinceReleased;\n\n\tbool mouseWasDown;\n\tRealTimeSince sinceClickedInside = 100;\n\n\tpublic ExternalWindowDock( Widget parent ) : base( parent )\n\t{\n\t\tLayout = Layout.Column();\n\n\t\tvar bar = Layout.AddRow();\n\t\tbar.Margin = 4;\n\t\tbar.Spacing = 4;\n\n\t\twindowList = bar.Add( new ComboBox( this ), 1 );\n\t\twindowList.ToolTip = \"Open windows (title \u2014 process)\";\n\n\t\tvar refresh = bar.Add( new IconButton( \"refresh\", RefreshList, this ) );\n\t\trefresh.ToolTip = \"Refresh the window list\";\n\n\t\tdockButton = bar.Add( new Button( \"Dock\", \"input\", this ) );\n\t\tdockButton.Clicked = DockSelected;\n\n\t\treleaseButton = bar.Add( new Button( \"Release\", \"output\", this ) );\n\t\treleaseButton.Clicked = Release;\n\n\t\tvar add = bar.Add( new IconButton( \"add\", OpenAnotherPanel, this ) );\n\t\tadd.ToolTip = \"Open another Window Dock panel\";\n\n\t\t// Keystrokes meant for the docked app must never land on these (a stray space would press Dock).\n\t\tforeach ( var control in new Widget[] { windowList, refresh, dockButton, releaseButton, add } )\n\t\t\tcontrol.FocusMode = FocusMode.None;\n\n\t\thost = Layout.Add( new HostWidget( this ), 1 );\n\t\thost.Resized = FitToHost;\n\n\t\tstatus = host.Layout.Add( new Label( \"Pick a window and press Dock.\", host ) );\n\t\tstatus.Alignment = TextFlag.Center;\n\n\t\tRefreshList();\n\t\tUpdateButtons();\n\t}\n\n\tvoid RefreshList()\n\t{\n\t\tlistedWindows = Win32.ListDockableWindows( docked );\n\t\twindowList.Clear();\n\n\t\tforeach ( var hwnd in listedWindows )\n\t\t\twindowList.AddItem( Win32.Describe( hwnd ) );\n\n\t\tif ( listedWindows.Count == 0 )\n\t\t\twindowList.AddItem( \"No dockable windows found\", enabled: false );\n\t}\n\n\t/// <summary>\n\t/// Extra panels are registered with the dock manager at runtime, so they get a View menu entry\n\t/// for this session but aren't recreated after an editor restart.\n\t/// </summary>\n\tvoid OpenAnotherPanel()\n\t{\n\t\tvar manager = EditorWindow.DockManager;\n\n\t\tvar number = 2;\n\t\twhile ( manager.FindDockWidget( $\"Window Dock {number}\" ) is not null )\n\t\t\tnumber++;\n\n\t\tvar panel = manager.AddDock( $\"Window Dock {number}\", \"picture_in_picture\", new ExternalWindowDock( null ), DockArea.Center, manager.FindDockWidget( this ) );\n\t\tpanel.SetAsCurrentTab();\n\t}\n\n\tvoid DockSelected()\n\t{\n\t\t// One window per panel; Release first (or use +) so a click can't silently swap it out.\n\t\tif ( docked != IntPtr.Zero )\n\t\t\treturn;\n\n\t\tvar index = windowList.CurrentIndex;\n\t\tif ( index < 0 || index >= listedWindows.Count )\n\t\t\treturn;\n\n\t\tvar target = listedWindows[index];\n\t\tif ( !Win32.IsWindow( target ) )\n\t\t{\n\t\t\tSetStatus( \"That window has closed.\" );\n\t\t\tRefreshList();\n\t\t\treturn;\n\t\t}\n\n\t\thostHandle = NativeHandle( host );\n\t\tif ( hostHandle == IntPtr.Zero )\n\t\t{\n\t\t\tSetStatus( \"Couldn't get the panel's native window handle (s&box internals changed?).\" );\n\t\t\treturn;\n\t\t}\n\n\t\t// Restore a maximized/minimized window first so the saved rect is its normal size.\n\t\tif ( Win32.IsZoomed( target ) || Win32.IsIconic( target ) )\n\t\t\tWin32.ShowWindow( target, Win32.SW_RESTORE );\n\n\t\tsavedStyle = Win32.TopLevelStyle( Win32.GetWindowLong( target, Win32.GWL_STYLE ) );\n\t\tsavedExStyle = Win32.GetWindowLong( target, Win32.GWL_EXSTYLE );\n\t\tWin32.GetWindowRect( target, out savedRect );\n\n\t\t// Make sure Qt doesn't paint over the hosted window.\n\t\tWin32.SetWindowLong( hostHandle, Win32.GWL_STYLE, Win32.GetWindowLong( hostHandle, Win32.GWL_STYLE ) | Win32.WS_CLIPCHILDREN );\n\n\t\t// Win32 wants WS_CHILD set (and WS_POPUP cleared) before SetParent.\n\t\tvar style = (savedStyle & ~Win32.FrameStyles) | Win32.WS_CHILD | Win32.WS_VISIBLE;\n\t\tvar exStyle = savedExStyle & ~Win32.FrameExStyles;\n\t\tWin32.SetWindowLong( target, Win32.GWL_STYLE, style );\n\t\tWin32.SetWindowLong( target, Win32.GWL_EXSTYLE, exStyle );\n\n\t\tif ( Win32.SetParent( target, hostHandle ) == IntPtr.Zero && Marshal.GetLastWin32Error() != 0 )\n\t\t{\n\t\t\tvar error = Marshal.GetLastWin32Error();\n\t\t\tWin32.SetWindowLong( target, Win32.GWL_STYLE, savedStyle );\n\t\t\tWin32.SetWindowLong( target, Win32.GWL_EXSTYLE, savedExStyle );\n\t\t\tWin32.SetWindowPos( target, IntPtr.Zero, 0, 0, 0, 0, Win32.SWP_NOMOVE | Win32.SWP_NOSIZE | Win32.SWP_NOZORDER | Win32.SWP_FRAMECHANGED );\n\t\t\tSetStatus( $\"Windows refused to dock that window (error {error}).\" );\n\t\t\treturn;\n\t\t}\n\n\t\tdocked = target;\n\t\tstatus.Visible = false;\n\t\tFitToHost();\n\t\tWin32.ShowWindow( docked, Win32.SW_SHOW );\n\t\tWin32.SetFocus( docked );\n\n\t\tLog.Info( $\"Window Dock: docked {Win32.Describe( docked )}\" );\n\t\tRefreshList();\n\t\tUpdateButtons();\n\t}\n\n\tvoid Release() => Release( true );\n\n\tvoid Release( bool updateUi )\n\t{\n\t\tif ( docked == IntPtr.Zero )\n\t\t\treturn;\n\n\t\tvar window = docked;\n\t\tdocked = IntPtr.Zero;\n\n\t\tif ( Win32.IsWindow( window ) )\n\t\t{\n\t\t\tWin32.SetParent( window, IntPtr.Zero );\n\t\t\tWin32.SetWindowLong( window, Win32.GWL_STYLE, savedStyle );\n\t\t\tWin32.SetWindowLong( window, Win32.GWL_EXSTYLE, savedExStyle );\n\t\t\tWin32.SetWindowPos( window, IntPtr.Zero, savedRect.Left, savedRect.Top,\n\t\t\t\tsavedRect.Right - savedRect.Left, savedRect.Bottom - savedRect.Top,\n\t\t\t\tWin32.SWP_NOZORDER | Win32.SWP_FRAMECHANGED | Win32.SWP_SHOWWINDOW );\n\n\t\t\t// Docking joined the app's input queue to the editor's, and un-parenting doesn't split\n\t\t\t// them again; while they stay joined the app can't take keyboard input on its own.\n\t\t\tWin32.DetachInput( window, hostHandle );\n\t\t\tWin32.SetForegroundWindow( window );\n\n\t\t\treleased = window;\n\t\t\treleasedStyle = savedStyle;\n\t\t\tsinceReleased = 0;\n\t\t}\n\n\t\tif ( !updateUi )\n\t\t\treturn;\n\n\t\tSetStatus( \"Pick a window and press Dock.\" );\n\t\tRefreshList();\n\t\tUpdateButtons();\n\t}\n\n\tvoid FitToHost()\n\t{\n\t\tif ( docked == IntPtr.Zero )\n\t\t\treturn;\n\n\t\tif ( hostHandle == IntPtr.Zero || !Win32.GetClientRect( hostHandle, out var client ) )\n\t\t\treturn;\n\n\t\tWin32.MoveWindow( docked, 0, 0, client.Right, client.Bottom, true );\n\t}\n\n\t/// <summary>\n\t/// Apps that draw their own title bar (Electron, Chromium) drag and resize themselves, and their\n\t/// minimize/restore buttons still work. Keep the window filling the panel whatever it does.\n\t/// </summary>\n\tvoid HoldInPlace()\n\t{\n\t\tif ( Win32.IsIconic( docked ) )\n\t\t\tWin32.ShowWindow( docked, Win32.SW_RESTORE );\n\n\t\t// Ends the drag/resize loop the app started from its title bar.\n\t\tif ( Win32.IsInMoveSize( docked ) )\n\t\t\tWin32.PostMessage( docked, Win32.WM_CANCELMODE, IntPtr.Zero, IntPtr.Zero );\n\n\t\tWin32.GetWindowRect( docked, out var window );\n\t\tWin32.GetWindowRect( hostHandle, out var panel );\n\t\tif ( window.Left != panel.Left || window.Top != panel.Top || window.Right != panel.Right || window.Bottom != panel.Bottom )\n\t\t\tFitToHost();\n\t}\n\n\t/// <summary>\n\t/// Clicking the docked window activates the editor, and Qt then pulls keyboard focus back to\n\t/// itself. For a moment after a click inside, hand focus back to the docked window.\n\t/// Clicking back into the editor doesn't return focus by itself: Qt still thinks the widget it\n\t/// last focused (e.g. the game viewport) has it, so it never asks Windows for it. Do that for Qt.\n\t/// </summary>\n\tvoid FocusOnClick()\n\t{\n\t\tvar down = Win32.AnyMouseButtonDown();\n\t\tif ( down && !mouseWasDown )\n\t\t{\n\t\t\tif ( Win32.IsUnderCursor( docked ) )\n\t\t\t{\n\t\t\t\tsinceClickedInside = 0;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tsinceClickedInside = 100;\n\n\t\t\t\tif ( Win32.HasFocus( docked ) )\n\t\t\t\t\tWin32.FocusEditorWindowUnderCursor();\n\t\t\t}\n\t\t}\n\n\t\tmouseWasDown = down;\n\n\t\tif ( sinceClickedInside < 0.3f && !Win32.HasFocus( docked ) )\n\t\t\tWin32.SetFocus( docked );\n\t}\n\n\tvoid SetStatus( string text )\n\t{\n\t\tstatus.Text = text;\n\t\tstatus.Visible = docked == IntPtr.Zero;\n\t}\n\n\tvoid UpdateButtons()\n\t{\n\t\tdockButton.Enabled = docked == IntPtr.Zero;\n\t\treleaseButton.Enabled = docked != IntPtr.Zero;\n\t}\n\n\t[EditorEvent.Frame]\n\tvoid KeepDocked()\n\t{\n\t\tGuardReleased();\n\n\t\tif ( docked == IntPtr.Zero )\n\t\t\treturn;\n\n\t\t// The hosted app closed its window.\n\t\tif ( !Win32.IsWindow( docked ) )\n\t\t{\n\t\t\tdocked = IntPtr.Zero;\n\t\t\tSetStatus( \"The docked window was closed.\" );\n\t\t\tRefreshList();\n\t\t\tUpdateButtons();\n\t\t\treturn;\n\t\t}\n\n\t\t// A hotload adds new fields at their default.\n\t\tif ( hostHandle == IntPtr.Zero )\n\t\t\thostHandle = NativeHandle( host );\n\n\t\tHoldInPlace();\n\t\tFocusOnClick();\n\t}\n\n\t/// <summary>\n\t/// A top-level window that still has WS_CHILD can't be activated: it takes no typing and stays\n\t/// behind everything. Some apps re-apply the style they last saw, so watch a released window briefly.\n\t/// </summary>\n\tvoid GuardReleased()\n\t{\n\t\tif ( released == IntPtr.Zero )\n\t\t\treturn;\n\n\t\tif ( sinceReleased > 3 || !Win32.IsWindow( released ) )\n\t\t{\n\t\t\treleased = IntPtr.Zero;\n\t\t\treturn;\n\t\t}\n\n\t\tvar style = Win32.GetWindowLong( released, Win32.GWL_STYLE );\n\t\tif ( (style & Win32.WS_CHILD) == 0 || !Win32.IsTopLevel( released ) )\n\t\t\treturn;\n\n\t\tLog.Warning( $\"Window Dock: {Win32.Describe( released )} went back to a child style after release (0x{style:X8}); restoring 0x{releasedStyle:X8}\" );\n\t\tWin32.SetWindowLong( released, Win32.GWL_STYLE, releasedStyle );\n\t\tWin32.SetWindowPos( released, IntPtr.Zero, 0, 0, 0, 0, Win32.SWP_NOMOVE | Win32.SWP_NOSIZE | Win32.SWP_NOZORDER | Win32.SWP_FRAMECHANGED );\n\t}\n\n\t// Destroying the editor's windows would take a docked window down with them,\n\t// so hand it back while ours still exist.\n\t[Event( \"app.exit\" )]\n\tvoid ReleaseOnExit() => Release( false );\n\n\t// Best effort: by now Qt has usually destroyed our native window (and the child with it).\n\tpublic override void OnDestroyed()\n\t{\n\t\tRelease( false );\n\t\tbase.OnDestroyed();\n\t}\n\n\t/// <summary>\n\t/// Editor.Widget keeps its QWidget internal; read it and ask Qt for the HWND (this also makes the widget native).\n\t/// </summary>\n\tstatic IntPtr NativeHandle( Widget widget )\n\t{\n\t\tvar field = typeof( Widget ).GetField( \"_widget\", BindingFlags.Instance | BindingFlags.NonPublic );\n\t\tvar native = field?.GetValue( widget );\n\t\tvar winId = native?.GetType().GetMethod( \"winId\", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, Type.EmptyTypes );\n\n\t\treturn winId?.Invoke( native, null ) is IntPtr handle ? handle : IntPtr.Zero;\n\t}\n\n\tclass HostWidget : Widget\n\t{\n\t\tpublic Action Resized;\n\n\t\tpublic HostWidget( Widget parent ) : base( parent )\n\t\t{\n\t\t\tLayout = Layout.Column();\n\t\t\tSetSizeMode( SizeMode.Flexible, SizeMode.Flexible );\n\t\t}\n\n\t\tprotected override void OnResize()\n\t\t{\n\t\t\tbase.OnResize();\n\t\t\tResized?.Invoke();\n\t\t}\n\t}\n\n\tstatic class Win32\n\t{\n\t\tpublic const int GWL_STYLE = -16;\n\t\tpublic const int GWL_EXSTYLE = -20;\n\n\t\tpublic const long WS_CHILD = 0x40000000;\n\t\tpublic const long WS_VISIBLE = 0x10000000;\n\t\tpublic const long WS_CLIPCHILDREN = 0x02000000;\n\t\tconst long WS_POPUP = 0x80000000;\n\t\tconst long WS_CAPTION = 0x00C00000;\n\t\tconst long WS_THICKFRAME = 0x00040000;\n\t\tconst long WS_SYSMENU = 0x00080000;\n\t\tconst long WS_MINIMIZEBOX = 0x00020000;\n\t\tconst long WS_MAXIMIZEBOX = 0x00010000;\n\t\tpublic const long FrameStyles = WS_POPUP | WS_CAPTION | WS_THICKFRAME | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;\n\n\t\tconst long WS_EX_DLGMODALFRAME = 0x00000001;\n\t\tconst long WS_EX_TOOLWINDOW = 0x00000080;\n\t\tconst long WS_EX_WINDOWEDGE = 0x00000100;\n\t\tconst long WS_EX_CLIENTEDGE = 0x00000200;\n\t\tconst long WS_EX_STATICEDGE = 0x00020000;\n\t\tconst long WS_EX_APPWINDOW = 0x00040000;\n\t\tpublic const long FrameExStyles = WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE | WS_EX_APPWINDOW;\n\n\t\tpublic const int SW_SHOW = 5;\n\t\tpublic const int SW_RESTORE = 9;\n\n\t\tpublic const uint SWP_NOSIZE = 0x0001;\n\t\tpublic const uint SWP_NOMOVE = 0x0002;\n\t\tpublic const uint SWP_NOZORDER = 0x0004;\n\t\tpublic const uint SWP_FRAMECHANGED = 0x0020;\n\t\tpublic const uint SWP_SHOWWINDOW = 0x0040;\n\n\t\tpublic const uint WM_CANCELMODE = 0x001F;\n\n\t\tconst uint GW_OWNER = 4;\n\t\tconst int DWMWA_CLOAKED = 14;\n\t\tconst int GUI_INMOVESIZE = 0x0002;\n\n\t\t[StructLayout( LayoutKind.Sequential )]\n\t\tpublic struct RECT { public int Left, Top, Right, Bottom; }\n\n\t\t[StructLayout( LayoutKind.Sequential )]\n\t\tstruct POINT { public int X, Y; }\n\n\t\t[StructLayout( LayoutKind.Sequential )]\n\t\tstruct GUITHREADINFO\n\t\t{\n\t\t\tpublic int cbSize;\n\t\t\tpublic int flags;\n\t\t\tpublic IntPtr hwndActive, hwndFocus, hwndCapture, hwndMenuOwner, hwndMoveSize, hwndCaret;\n\t\t\tpublic RECT rcCaret;\n\t\t}\n\n\t\tdelegate bool EnumWindowsProc( IntPtr hwnd, IntPtr lParam );\n\n\t\t[DllImport( \"user32.dll\" )] static extern bool EnumWindows( EnumWindowsProc callback, IntPtr lParam );\n\t\t[DllImport( \"user32.dll\" )] static extern bool IsWindowVisible( IntPtr hwnd );\n\t\t[DllImport( \"user32.dll\" )] static extern IntPtr GetWindow( IntPtr hwnd, uint cmd );\n\t\t[DllImport( \"user32.dll\" )] static extern int GetWindowTextLength( IntPtr hwnd );\n\t\t[DllImport( \"user32.dll\", CharSet = CharSet.Unicode )] static extern int GetWindowText( IntPtr hwnd, StringBuilder text, int max );\n\t\t[DllImport( \"user32.dll\", CharSet = CharSet.Unicode )] static extern int GetClassName( IntPtr hwnd, StringBuilder text, int max );\n\t\t[DllImport( \"user32.dll\" )] static extern uint GetWindowThreadProcessId( IntPtr hwnd, out uint processId );\n\t\t[DllImport( \"user32.dll\", EntryPoint = \"GetWindowLongPtrW\" )] static extern IntPtr GetWindowLongPtr( IntPtr hwnd, int index );\n\t\t[DllImport( \"user32.dll\", EntryPoint = \"SetWindowLongPtrW\" )] static extern IntPtr SetWindowLongPtr( IntPtr hwnd, int index, IntPtr value );\n\t\t[DllImport( \"dwmapi.dll\" )] static extern int DwmGetWindowAttribute( IntPtr hwnd, int attribute, out int value, int size );\n\n\t\t[DllImport( \"user32.dll\" )] public static extern bool IsWindow( IntPtr hwnd );\n\t\t[DllImport( \"user32.dll\" )] public static extern bool IsZoomed( IntPtr hwnd );\n\t\t[DllImport( \"user32.dll\" )] public static extern bool IsIconic( IntPtr hwnd );\n\t\t[DllImport( \"user32.dll\" )] public static extern bool ShowWindow( IntPtr hwnd, int cmd );\n\t\t[DllImport( \"user32.dll\" )] public static extern bool GetWindowRect( IntPtr hwnd, out RECT rect );\n\t\t[DllImport( \"user32.dll\", SetLastError = true )] public static extern IntPtr SetParent( IntPtr child, IntPtr parent );\n\t\t[DllImport( \"user32.dll\" )] public static extern bool MoveWindow( IntPtr hwnd, int x, int y, int width, int height, bool repaint );\n\t\t[DllImport( \"user32.dll\" )] public static extern bool SetWindowPos( IntPtr hwnd, IntPtr after, int x, int y, int width, int height, uint flags );\n\t\t[DllImport( \"user32.dll\" )] public static extern bool SetForegroundWindow( IntPtr hwnd );\n\t\t[DllImport( \"user32.dll\" )] public static extern bool GetClientRect( IntPtr hwnd, out RECT rect );\n\t\t[DllImport( \"user32.dll\" )] public static extern bool PostMessage( IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam );\n\t\t[DllImport( \"user32.dll\" )] public static extern IntPtr SetFocus( IntPtr hwnd );\n\t\t[DllImport( \"user32.dll\" )] static extern IntPtr GetFocus();\n\t\t[DllImport( \"user32.dll\" )] static extern bool IsChild( IntPtr parent, IntPtr hwnd );\n\t\t[DllImport( \"user32.dll\" )] static extern bool GetCursorPos( out POINT point );\n\t\t[DllImport( \"user32.dll\" )] static extern IntPtr WindowFromPoint( POINT point );\n\t\t[DllImport( \"user32.dll\" )] static extern short GetAsyncKeyState( int key );\n\t\t[DllImport( \"user32.dll\" )] static extern bool GetGUIThreadInfo( uint threadId, ref GUITHREADINFO info );\n\n\t\tstatic bool IsSelfOrChild( IntPtr window, IntPtr hwnd ) => hwnd != IntPtr.Zero && (hwnd == window || IsChild( window, hwnd ));\n\n\t\tpublic static bool HasFocus( IntPtr window ) => IsSelfOrChild( window, GetFocus() );\n\n\t\tpublic static bool IsUnderCursor( IntPtr window ) => GetCursorPos( out var point ) && IsSelfOrChild( window, WindowFromPoint( point ) );\n\n\t\t/// <summary>\n\t\t/// Give keyboard focus to the editor window under the cursor (the game viewport, the main window...).\n\t\t/// Clicks on other apps or other docked windows are left alone.\n\t\t/// </summary>\n\t\tpublic static void FocusEditorWindowUnderCursor()\n\t\t{\n\t\t\tif ( !GetCursorPos( out var point ) )\n\t\t\t\treturn;\n\n\t\t\tvar hwnd = WindowFromPoint( point );\n\t\t\tif ( hwnd == IntPtr.Zero )\n\t\t\t\treturn;\n\n\t\t\tGetWindowThreadProcessId( hwnd, out var pid );\n\t\t\tif ( pid == (uint)Environment.ProcessId )\n\t\t\t\tSetFocus( hwnd );\n\t\t}\n\n\t\t// Left, right and middle buttons.\n\t\tpublic static bool AnyMouseButtonDown() => ((GetAsyncKeyState( 0x01 ) | GetAsyncKeyState( 0x02 ) | GetAsyncKeyState( 0x04 )) & 0x8000) != 0;\n\n\t\t[DllImport( \"user32.dll\" )] static extern bool AttachThreadInput( uint attach, uint attachTo, bool join );\n\t\t[DllImport( \"user32.dll\" )] static extern IntPtr GetAncestor( IntPtr hwnd, uint flags );\n\t\t[DllImport( \"user32.dll\" )] static extern IntPtr GetDesktopWindow();\n\n\t\tconst long OverlappedFrame = WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;\n\n\t\tpublic static bool IsTopLevel( IntPtr hwnd ) => GetAncestor( hwnd, 1 ) == GetDesktopWindow();\n\n\t\t/// <summary>\n\t\t/// The style to hand a window back with. A window docked while already child-styled (left broken\n\t\t/// by an earlier release) gets a normal overlapped frame instead of its broken style.\n\t\t/// </summary>\n\t\tpublic static long TopLevelStyle( long style ) => (style & WS_CHILD) == 0 ? style : (style & ~WS_CHILD) | OverlappedFrame;\n\n\t\tpublic static void DetachInput( IntPtr window, IntPtr editorWindow )\n\t\t{\n\t\t\tvar appThread = GetWindowThreadProcessId( window, out _ );\n\t\t\tvar editorThread = GetWindowThreadProcessId( editorWindow, out _ );\n\t\t\tif ( appThread == 0 || editorThread == 0 || appThread == editorThread )\n\t\t\t\treturn;\n\n\t\t\tAttachThreadInput( appThread, editorThread, false );\n\t\t\tAttachThreadInput( editorThread, appThread, false );\n\t\t}\n\n\t\tpublic static bool IsInMoveSize( IntPtr window )\n\t\t{\n\t\t\tvar info = new GUITHREADINFO { cbSize = Marshal.SizeOf<GUITHREADINFO>() };\n\t\t\treturn GetGUIThreadInfo( GetWindowThreadProcessId( window, out _ ), ref info )\n\t\t\t\t&& (info.flags & GUI_INMOVESIZE) != 0 && IsSelfOrChild( window, info.hwndMoveSize );\n\t\t}\n\n\t\tpublic static long GetWindowLong( IntPtr hwnd, int index ) => GetWindowLongPtr( hwnd, index ).ToInt64();\n\t\tpublic static void SetWindowLong( IntPtr hwnd, int index, long value ) => SetWindowLongPtr( hwnd, index, new IntPtr( value ) );\n\n\t\t/// <summary>\n\t\t/// Visible, titled, unowned top-level windows from other processes - roughly what the taskbar shows.\n\t\t/// </summary>\n\t\tpublic static List<IntPtr> ListDockableWindows( IntPtr include )\n\t\t{\n\t\t\tvar ownProcess = (uint)Environment.ProcessId;\n\t\t\tvar windows = new List<IntPtr>();\n\n\t\t\tEnumWindows( ( hwnd, _ ) =>\n\t\t\t{\n\t\t\t\tif ( hwnd == include )\n\t\t\t\t\treturn true;\n\n\t\t\t\tif ( !IsWindowVisible( hwnd ) || GetWindowTextLength( hwnd ) == 0 || GetWindow( hwnd, GW_OWNER ) != IntPtr.Zero )\n\t\t\t\t\treturn true;\n\n\t\t\t\tif ( (GetWindowLong( hwnd, GWL_EXSTYLE ) & WS_EX_TOOLWINDOW) != 0 )\n\t\t\t\t\treturn true;\n\n\t\t\t\tGetWindowThreadProcessId( hwnd, out var pid );\n\t\t\t\tif ( pid == ownProcess )\n\t\t\t\t\treturn true;\n\n\t\t\t\t// Suspended/background UWP frames report visible but are cloaked.\n\t\t\t\tif ( DwmGetWindowAttribute( hwnd, DWMWA_CLOAKED, out var cloaked, sizeof( int ) ) == 0 && cloaked != 0 )\n\t\t\t\t\treturn true;\n\n\t\t\t\t// UWP/WinUI hosts (Calculator, Settings, Terminal...) can't be reparented properly.\n\t\t\t\tvar className = ClassName( hwnd );\n\t\t\t\tif ( className is \"ApplicationFrameWindow\" or \"Windows.UI.Core.CoreWindow\" or \"Progman\" )\n\t\t\t\t\treturn true;\n\n\t\t\t\twindows.Add( hwnd );\n\t\t\t\treturn true;\n\t\t\t}, IntPtr.Zero );\n\n\t\t\treturn windows;\n\t\t}\n\n\t\tpublic static string Describe( IntPtr hwnd )\n\t\t{\n\t\t\tvar title = new StringBuilder( GetWindowTextLength( hwnd ) + 1 );\n\t\t\tGetWindowText( hwnd, title, title.Capacity );\n\n\t\t\tGetWindowThreadProcessId( hwnd, out var pid );\n\t\t\tstring process;\n\t\t\ttry { process = Process.GetProcessById( (int)pid ).ProcessName; }\n\t\t\tcatch { process = $\"pid {pid}\"; }\n\n\t\t\treturn $\"{title} \u2014 {process}\";\n\t\t}\n\n\t\tstatic string ClassName( IntPtr hwnd )\n\t\t{\n\t\t\tvar name = new StringBuilder( 256 );\n\t\t\tGetClassName( hwnd, name, name.Capacity );\n\t\t\treturn name.ToString();\n\t\t}\n\t}\n}\n"
        }
    ]
}