mirror of
https://github.com/stride3d/stride.git
synced 2026-10-12 02:23:23 +00:00
155 lines
6.0 KiB
C#
155 lines
6.0 KiB
C#
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
|
|
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.Runtime.ExceptionServices;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Threading;
|
|
using Xunit;
|
|
using Stride.Core.Diagnostics;
|
|
using Stride.Core.Presentation.Interop;
|
|
using Stride.Core.Presentation.Windows;
|
|
|
|
namespace Stride.Core.Presentation.Tests
|
|
{
|
|
internal static class WindowManagerHelper
|
|
{
|
|
private const int TimeoutDelay = 10000;
|
|
// This must remains a field to prevent garbage collection!
|
|
private static TaskCompletionSource<int> nextWindowShown = new TaskCompletionSource<int>();
|
|
private static TaskCompletionSource<int> nextWindowHidden = new TaskCompletionSource<int>();
|
|
private static bool forwardingToConsole;
|
|
|
|
public static Task Timeout => !Debugger.IsAttached ? Task.Delay(TimeoutDelay) : new TaskCompletionSource<int>().Task;
|
|
|
|
public static Task NextWindowShown => nextWindowShown.Task;
|
|
|
|
public static Task NextWindowHidden => nextWindowHidden.Task;
|
|
|
|
public static Task<Dispatcher> CreateUIThread()
|
|
{
|
|
var tcs = new TaskCompletionSource<Dispatcher>();
|
|
var thread = new Thread(() =>
|
|
{
|
|
tcs.SetResult(Dispatcher.CurrentDispatcher);
|
|
Dispatcher.Run();
|
|
})
|
|
{
|
|
Name = "Test UI thread",
|
|
IsBackground = true
|
|
};
|
|
thread.SetApartmentState(ApartmentState.STA);
|
|
thread.Start();
|
|
return tcs.Task;
|
|
}
|
|
|
|
public static void ShutdownUIThread(Dispatcher dispatcher)
|
|
{
|
|
Thread thread = null;
|
|
dispatcher.Invoke(() => thread = Thread.CurrentThread);
|
|
dispatcher.InvokeShutdown();
|
|
thread.Join();
|
|
}
|
|
|
|
public static async Task TaskWithTimeout(Task task)
|
|
{
|
|
await Task.WhenAny(task, Timeout);
|
|
if (task.Exception != null)
|
|
ExceptionDispatchInfo.Capture(task.Exception.InnerException).Throw();
|
|
|
|
Assert.True(task.IsCompleted, "Test timed out");
|
|
}
|
|
|
|
public static LoggerResult CreateLoggerResult(Logger logger)
|
|
{
|
|
var loggerResult = new LoggerResult();
|
|
logger.MessageLogged += (sender, e) => loggerResult.Log(e.Message);
|
|
if (!forwardingToConsole)
|
|
{
|
|
logger.MessageLogged += (sender, e) => Console.WriteLine(e.Message);
|
|
forwardingToConsole = true;
|
|
}
|
|
return loggerResult;
|
|
}
|
|
|
|
public static IDisposable InitWindowManager(Dispatcher dispatcher, out LoggerResult loggerResult)
|
|
{
|
|
var manager = new WindowManagerWrapper(dispatcher);
|
|
loggerResult = CreateLoggerResult(WindowManager.Logger);
|
|
return manager;
|
|
}
|
|
|
|
private class WindowManagerWrapper : IDisposable
|
|
{
|
|
private static Dispatcher uiDispatcher;
|
|
private static NativeHelper.WinEventDelegate winEventProc;
|
|
private static IntPtr hook;
|
|
private readonly WindowManager manager;
|
|
|
|
public WindowManagerWrapper(Dispatcher dispatcher)
|
|
{
|
|
uiDispatcher = dispatcher;
|
|
manager = uiDispatcher.Invoke(() =>
|
|
{
|
|
winEventProc = WinEventProc;
|
|
var processId = (uint)Process.GetCurrentProcess().Id;
|
|
hook = NativeHelper.SetWinEventHook(NativeHelper.EVENT_OBJECT_SHOW, NativeHelper.EVENT_OBJECT_HIDE, IntPtr.Zero, winEventProc, processId, 0, NativeHelper.WINEVENT_OUTOFCONTEXT);
|
|
if (hook == IntPtr.Zero) throw new InvalidOperationException("Unable to initialize the window manager.");
|
|
return new WindowManager(uiDispatcher);
|
|
});
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
uiDispatcher.Invoke(() =>
|
|
{
|
|
manager.Dispose();
|
|
if (!NativeHelper.UnhookWinEvent(hook)) throw new InvalidOperationException("An error occurred while disposing the window manager.");
|
|
hook = IntPtr.Zero;
|
|
winEventProc = null;
|
|
});
|
|
}
|
|
|
|
private static void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
|
|
{
|
|
if (hwnd == IntPtr.Zero)
|
|
return;
|
|
|
|
var rootHwnd = NativeHelper.GetAncestor(hwnd, NativeHelper.GetAncestorFlags.GetRoot);
|
|
if (rootHwnd != IntPtr.Zero && rootHwnd != hwnd)
|
|
return;
|
|
|
|
// idObject == 0 means it is the window itself, not a child object
|
|
if (eventType == NativeHelper.EVENT_OBJECT_SHOW && idObject == 0)
|
|
{
|
|
Assert.True(uiDispatcher.CheckAccess());
|
|
WindowShown(hwnd);
|
|
}
|
|
if (eventType == NativeHelper.EVENT_OBJECT_HIDE && idObject == 0)
|
|
{
|
|
Assert.True(uiDispatcher.CheckAccess());
|
|
WindowHidden();
|
|
}
|
|
}
|
|
|
|
private static void WindowShown(IntPtr hwnd)
|
|
{
|
|
if (!HwndHelper.HasStyleFlag(hwnd, NativeHelper.WS_VISIBLE))
|
|
return;
|
|
|
|
var oldTcs = nextWindowShown;
|
|
nextWindowShown = new TaskCompletionSource<int>();
|
|
oldTcs.SetResult(0);
|
|
}
|
|
|
|
private static void WindowHidden()
|
|
{
|
|
var oldTcs = nextWindowHidden;
|
|
nextWindowHidden = new TaskCompletionSource<int>();
|
|
oldTcs.SetResult(0);
|
|
}
|
|
}
|
|
}
|
|
}
|