{"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/// \u003Csummary\u003E\n/// Editor dock that hosts another application\u0027s 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/// \u003C/summary\u003E\n[Dock( \u0022Editor\u0022, \u0022Window Dock\u0022, \u0022picture_in_picture\u0022 )]\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\u003CIntPtr\u003E 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 = \u0022Open windows (title \u2014 process)\u0022;\n\n\t\tvar refresh = bar.Add( new IconButton( \u0022refresh\u0022, RefreshList, this ) );\n\t\trefresh.ToolTip = \u0022Refresh the window list\u0022;\n\n\t\tdockButton = bar.Add( new Button( \u0022Dock\u0022, \u0022input\u0022, this ) );\n\t\tdockButton.Clicked = DockSelected;\n\n\t\treleaseButton = bar.Add( new Button( \u0022Release\u0022, \u0022output\u0022, this ) );\n\t\treleaseButton.Clicked = Release;\n\n\t\tvar add = bar.Add( new IconButton( \u0022add\u0022, OpenAnotherPanel, this ) );\n\t\tadd.ToolTip = \u0022Open another Window Dock panel\u0022;\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( \u0022Pick a window and press Dock.\u0022, 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( \u0022No dockable windows found\u0022, enabled: false );\n\t}\n\n\t/// \u003Csummary\u003E\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\u0027t recreated after an editor restart.\n\t/// \u003C/summary\u003E\n\tvoid OpenAnotherPanel()\n\t{\n\t\tvar manager = EditorWindow.DockManager;\n\n\t\tvar number = 2;\n\t\twhile ( manager.FindDockWidget( $\u0022Window Dock {number}\u0022 ) is not null )\n\t\t\tnumber\u002B\u002B;\n\n\t\tvar panel = manager.AddDock( $\u0022Window Dock {number}\u0022, \u0022picture_in_picture\u0022, 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 \u002B) so a click can\u0027t 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 \u003C 0 || index \u003E= 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( \u0022That window has closed.\u0022 );\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( \u0022Couldn\u0027t get the panel\u0027s native window handle (s\u0026box internals changed?).\u0022 );\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\u0027t 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 \u0026 ~Win32.FrameStyles) | Win32.WS_CHILD | Win32.WS_VISIBLE;\n\t\tvar exStyle = savedExStyle \u0026 ~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 \u0026\u0026 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( $\u0022Windows refused to dock that window (error {error}).\u0022 );\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( $\u0022Window Dock: docked {Win32.Describe( docked )}\u0022 );\n\t\tRefreshList();\n\t\tUpdateButtons();\n\t}\n\n\tvoid Release() =\u003E 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\u0027s input queue to the editor\u0027s, and un-parenting doesn\u0027t split\n\t\t\t// them again; while they stay joined the app can\u0027t 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( \u0022Pick a window and press Dock.\u0022 );\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/// \u003Csummary\u003E\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/// \u003C/summary\u003E\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/// \u003Csummary\u003E\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\u0027t 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/// \u003C/summary\u003E\n\tvoid FocusOnClick()\n\t{\n\t\tvar down = Win32.AnyMouseButtonDown();\n\t\tif ( down \u0026\u0026 !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 \u003C 0.3f \u0026\u0026 !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( \u0022The docked window was closed.\u0022 );\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/// \u003Csummary\u003E\n\t/// A top-level window that still has WS_CHILD can\u0027t 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/// \u003C/summary\u003E\n\tvoid GuardReleased()\n\t{\n\t\tif ( released == IntPtr.Zero )\n\t\t\treturn;\n\n\t\tif ( sinceReleased \u003E 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 \u0026 Win32.WS_CHILD) == 0 || !Win32.IsTopLevel( released ) )\n\t\t\treturn;\n\n\t\tLog.Warning( $\u0022Window Dock: {Win32.Describe( released )} went back to a child style after release (0x{style:X8}); restoring 0x{releasedStyle:X8}\u0022 );\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\u0027s windows would take a docked window down with them,\n\t// so hand it back while ours still exist.\n\t[Event( \u0022app.exit\u0022 )]\n\tvoid ReleaseOnExit() =\u003E 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/// \u003Csummary\u003E\n\t/// Editor.Widget keeps its QWidget internal; read it and ask Qt for the HWND (this also makes the widget native).\n\t/// \u003C/summary\u003E\n\tstatic IntPtr NativeHandle( Widget widget )\n\t{\n\t\tvar field = typeof( Widget ).GetField( \u0022_widget\u0022, BindingFlags.Instance | BindingFlags.NonPublic );\n\t\tvar native = field?.GetValue( widget );\n\t\tvar winId = native?.GetType().GetMethod( \u0022winId\u0022, 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( \u0022user32.dll\u0022 )] static extern bool EnumWindows( EnumWindowsProc callback, IntPtr lParam );\n\t\t[DllImport( \u0022user32.dll\u0022 )] static extern bool IsWindowVisible( IntPtr hwnd );\n\t\t[DllImport( \u0022user32.dll\u0022 )] static extern IntPtr GetWindow( IntPtr hwnd, uint cmd );\n\t\t[DllImport( \u0022user32.dll\u0022 )] static extern int GetWindowTextLength( IntPtr hwnd );\n\t\t[DllImport( \u0022user32.dll\u0022, CharSet = CharSet.Unicode )] static extern int GetWindowText( IntPtr hwnd, StringBuilder text, int max );\n\t\t[DllImport( \u0022user32.dll\u0022, CharSet = CharSet.Unicode )] static extern int GetClassName( IntPtr hwnd, StringBuilder text, int max );\n\t\t[DllImport( \u0022user32.dll\u0022 )] static extern uint GetWindowThreadProcessId( IntPtr hwnd, out uint processId );\n\t\t[DllImport( \u0022user32.dll\u0022, EntryPoint = \u0022GetWindowLongPtrW\u0022 )] static extern IntPtr GetWindowLongPtr( IntPtr hwnd, int index );\n\t\t[DllImport( \u0022user32.dll\u0022, EntryPoint = \u0022SetWindowLongPtrW\u0022 )] static extern IntPtr SetWindowLongPtr( IntPtr hwnd, int index, IntPtr value );\n\t\t[DllImport( \u0022dwmapi.dll\u0022 )] static extern int DwmGetWindowAttribute( IntPtr hwnd, int attribute, out int value, int size );\n\n\t\t[DllImport( \u0022user32.dll\u0022 )] public static extern bool IsWindow( IntPtr hwnd );\n\t\t[DllImport( \u0022user32.dll\u0022 )] public static extern bool IsZoomed( IntPtr hwnd );\n\t\t[DllImport( \u0022user32.dll\u0022 )] public static extern bool IsIconic( IntPtr hwnd );\n\t\t[DllImport( \u0022user32.dll\u0022 )] public static extern bool ShowWindow( IntPtr hwnd, int cmd );\n\t\t[DllImport( \u0022user32.dll\u0022 )] public static extern bool GetWindowRect( IntPtr hwnd, out RECT rect );\n\t\t[DllImport( \u0022user32.dll\u0022, SetLastError = true )] public static extern IntPtr SetParent( IntPtr child, IntPtr parent );\n\t\t[DllImport( \u0022user32.dll\u0022 )] public static extern bool MoveWindow( IntPtr hwnd, int x, int y, int width, int height, bool repaint );\n\t\t[DllImport( \u0022user32.dll\u0022 )] public static extern bool SetWindowPos( IntPtr hwnd, IntPtr after, int x, int y, int width, int height, uint flags );\n\t\t[DllImport( \u0022user32.dll\u0022 )] public static extern bool SetForegroundWindow( IntPtr hwnd );\n\t\t[DllImport( \u0022user32.dll\u0022 )] public static extern bool GetClientRect( IntPtr hwnd, out RECT rect );\n\t\t[DllImport( \u0022user32.dll\u0022 )] public static extern bool PostMessage( IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam );\n\t\t[DllImport( \u0022user32.dll\u0022 )] public static extern IntPtr SetFocus( IntPtr hwnd );\n\t\t[DllImport( \u0022user32.dll\u0022 )] static extern IntPtr GetFocus();\n\t\t[DllImport( \u0022user32.dll\u0022 )] static extern bool IsChild( IntPtr parent, IntPtr hwnd );\n\t\t[DllImport( \u0022user32.dll\u0022 )] static extern bool GetCursorPos( out POINT point );\n\t\t[DllImport( \u0022user32.dll\u0022 )] static extern IntPtr WindowFromPoint( POINT point );\n\t\t[DllImport( \u0022user32.dll\u0022 )] static extern short GetAsyncKeyState( int key );\n\t\t[DllImport( \u0022user32.dll\u0022 )] static extern bool GetGUIThreadInfo( uint threadId, ref GUITHREADINFO info );\n\n\t\tstatic bool IsSelfOrChild( IntPtr window, IntPtr hwnd ) =\u003E hwnd != IntPtr.Zero \u0026\u0026 (hwnd == window || IsChild( window, hwnd ));\n\n\t\tpublic static bool HasFocus( IntPtr window ) =\u003E IsSelfOrChild( window, GetFocus() );\n\n\t\tpublic static bool IsUnderCursor( IntPtr window ) =\u003E GetCursorPos( out var point ) \u0026\u0026 IsSelfOrChild( window, WindowFromPoint( point ) );\n\n\t\t/// \u003Csummary\u003E\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/// \u003C/summary\u003E\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() =\u003E ((GetAsyncKeyState( 0x01 ) | GetAsyncKeyState( 0x02 ) | GetAsyncKeyState( 0x04 )) \u0026 0x8000) != 0;\n\n\t\t[DllImport( \u0022user32.dll\u0022 )] static extern bool AttachThreadInput( uint attach, uint attachTo, bool join );\n\t\t[DllImport( \u0022user32.dll\u0022 )] static extern IntPtr GetAncestor( IntPtr hwnd, uint flags );\n\t\t[DllImport( \u0022user32.dll\u0022 )] 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 ) =\u003E GetAncestor( hwnd, 1 ) == GetDesktopWindow();\n\n\t\t/// \u003Csummary\u003E\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/// \u003C/summary\u003E\n\t\tpublic static long TopLevelStyle( long style ) =\u003E (style \u0026 WS_CHILD) == 0 ? style : (style \u0026 ~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\u003CGUITHREADINFO\u003E() };\n\t\t\treturn GetGUIThreadInfo( GetWindowThreadProcessId( window, out _ ), ref info )\n\t\t\t\t\u0026\u0026 (info.flags \u0026 GUI_INMOVESIZE) != 0 \u0026\u0026 IsSelfOrChild( window, info.hwndMoveSize );\n\t\t}\n\n\t\tpublic static long GetWindowLong( IntPtr hwnd, int index ) =\u003E GetWindowLongPtr( hwnd, index ).ToInt64();\n\t\tpublic static void SetWindowLong( IntPtr hwnd, int index, long value ) =\u003E SetWindowLongPtr( hwnd, index, new IntPtr( value ) );\n\n\t\t/// \u003Csummary\u003E\n\t\t/// Visible, titled, unowned top-level windows from other processes - roughly what the taskbar shows.\n\t\t/// \u003C/summary\u003E\n\t\tpublic static List\u003CIntPtr\u003E ListDockableWindows( IntPtr include )\n\t\t{\n\t\t\tvar ownProcess = (uint)Environment.ProcessId;\n\t\t\tvar windows = new List\u003CIntPtr\u003E();\n\n\t\t\tEnumWindows( ( hwnd, _ ) =\u003E\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 ) \u0026 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 \u0026\u0026 cloaked != 0 )\n\t\t\t\t\treturn true;\n\n\t\t\t\t// UWP/WinUI hosts (Calculator, Settings, Terminal...) can\u0027t be reparented properly.\n\t\t\t\tvar className = ClassName( hwnd );\n\t\t\t\tif ( className is \u0022ApplicationFrameWindow\u0022 or \u0022Windows.UI.Core.CoreWindow\u0022 or \u0022Progman\u0022 )\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 ) \u002B 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 = $\u0022pid {pid}\u0022; }\n\n\t\t\treturn $\u0022{title} \u2014 {process}\u0022;\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"}]}